<commit_before>package main

import (
	db "github.com/carrot/burrow/db/postgres"
	"github.com/carrot/burrow/environment"
	"github.com/carrot/burrow/response"
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
	"github.com/tylerb/graceful"
	"log"
	"os"
	"time"
)

func main() {
	// ---------------------------
	// Setting Active Environment
	// ---------------------------

	if len(os.Args) > 1 {
		err := environment.Set(os.Args[1])
		if err != nil {
			log.Fatal(err)
		}
	} else {
		log.Fatal("Running requires an environment argument")
	}

	// ---------
	// Database
	// ---------

	db.Open()
	defer db.Close()

	// -----
	// Echo
	// -----

	e := echo.New()

	// -----------
	// Middleware
	// -----------

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// -------------------
	// HTTP Error Handler
	// -------------------

	e.SetHTTPErrorHandler(func(err error, context echo.Context) {
		httpError, ok := err.(*echo.HTTPError)
		if ok {
			response := response.New(context)
			response.SetResponse(httpError.Code, nil)
			response.Render()
		}
	})

	// -------
	// Routes
	// -------

	prepareRoutes(e)

	// ----
	// Run
	// ----

	port := environment.GetEnvVar(environment.PORT)
	std := standard.New(":" + port)
	std.SetHandler(e)
	log.Println("Server starting on :" + port)
	graceful.ListenAndServe(std.Server, 5*time.Second) // Graceful shutdown
}
<commit_msg>Log errors fixes #33<commit_after>package main

import (
	"fmt"
	db "github.com/carrot/burrow/db/postgres"
	"github.com/carrot/burrow/environment"
	"github.com/carrot/burrow/response"
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
	"github.com/labstack/gommon/color"
	"github.com/tylerb/graceful"
	"log"
	"os"
	"time"
)

func main() {
	// ---------------------------
	// Setting Active Environment
	// ---------------------------

	if len(os.Args) > 1 {
		err := environment.Set(os.Args[1])
		if err != nil {
			log.Fatal(err)
		}
	} else {
		log.Fatal("Running requires an environment argument")
	}

	// ---------
	// Database
	// ---------

	db.Open()
	defer db.Close()

	// -----
	// Echo
	// -----

	e := echo.New()

	// -----------
	// Middleware
	// -----------

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// -------------------
	// HTTP Error Handler
	// -------------------

	e.SetHTTPErrorHandler(func(err error, context echo.Context) {
		fmt.Println(color.Red(err))
		httpError, ok := err.(*echo.HTTPError)
		if ok {
			response := response.New(context)
			response.SetResponse(httpError.Code, nil)
			response.Render()
		}
	})

	// -------
	// Routes
	// -------

	prepareRoutes(e)

	// ----
	// Run
	// ----

	port := environment.GetEnvVar(environment.PORT)
	std := standard.New(":" + port)
	std.SetHandler(e)
	log.Println("Server starting on :" + port)
	graceful.ListenAndServe(std.Server, 5*time.Second) // Graceful shutdown
}
<|endoftext|>


<commit_before>package telegram

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type ParseMode string

// Parse modes
const (
	ModeNone     ParseMode = ""
	ModeMarkdown ParseMode = "Markdown"
	ModeHTML     ParseMode = "HTML"
)

// Bot represent a Telegram bot.
type Bot struct {
	token     string
	baseURL   string
	client    *http.Client
	messageCh chan *Message
}

// New creates a new Telegram bot with the given token, which is given by
// Botfather. See https://core.telegram.org/bots#botfather
func New(token string) *Bot {
	return &Bot{
		token:     token,
		baseURL:   fmt.Sprintf("https://api.telegram.org/bot%v/", token),
		client:    &http.Client{Timeout: 30 * time.Second},
		messageCh: make(chan *Message),
	}
}

func (b *Bot) Handler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer w.WriteHeader(http.StatusOK)

		var u Update
		json.NewDecoder(r.Body).Decode(&u)
		b.messageCh <- &u.Payload
	}
}

func (b *Bot) Messages() <-chan *Message {
	return b.messageCh
}

// SetWebhook assigns bot's webhook url with the given url.
func (b *Bot) SetWebhook(webhook string) error {
	params := url.Values{}
	params.Set("url", webhook)

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	err := b.sendCommand(nil, "setWebhook", params, &r)
	if err != nil {
		return err
	}

	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

// SendMessage sends text message to the recipient. Callers can send plain
// text or markdown messages by setting mode parameter.
func (b *Bot) SendMessage(recipient int64, message string, opts *SendOptions) (Message, error) {
	params := url.Values{
		"chat_id": {strconv.FormatInt(recipient, 10)},
		"text":    {message},
	}

	mapSendOptions(&params, opts)

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
		Message Message
	}
	b.sendCommand(nil, "sendMessage", params, &r)

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

func (b *Bot) forwardMessage(recipient User, message Message) (Message, error) {
	panic("not implemented yet")
}

// SendPhoto sends given photo to recipient. Only remote URLs are supported for now.
// A trivial example is:
//
//  b := bot.New("your-token-here")
//  photo := bot.Photo{URL: "http://i.imgur.com/6S9naG6.png"}
//  err := b.SendPhoto(recipient, photo, "sample image", nil)
func (b *Bot) SendPhoto(recipient int64, photo Photo, opts *SendOptions) (Message, error) {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("caption", photo.Caption)

	mapSendOptions(&params, opts)
	var r struct {
		OK      bool    `json:"ok"`
		Desc    string  `json:"description"`
		ErrCode int     `json:"error_code"`
		Message Message `json:"message"`
	}

	var err error
	if photo.Exists() {
		params.Set("photo", photo.FileID)
		err = b.sendCommand(nil, "sendPhoto", params, &r)
	} else if photo.URL != "" {
		params.Set("photo", photo.URL)
		err = b.sendCommand(nil, "sendPhoto", params, &r)
	} else {
		err = b.sendFile("sendPhoto", photo.File, "photo", params, &r)
	}

	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

func (b *Bot) sendFile(method string, f File, form string, params url.Values, v interface{}) error {
	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	part, err := w.CreateFormFile(form, f.Name)
	if err != nil {
		return err
	}

	_, err = io.Copy(part, f.Body)
	if err != nil {
		return err
	}

	for k, v := range params {
		w.WriteField(k, v[0])
	}

	err = w.Close()
	if err != nil {
		return err
	}

	resp, err := b.client.Post(b.baseURL+method, w.FormDataContentType(), &buf)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return json.NewDecoder(resp.Body).Decode(&v)
}

// SendAudio sends audio files, if you want Telegram clients to display
// them in the music player. audio must be in the .mp3 format and must not
// exceed 50 MB in size.
func (b *Bot) sendAudio(recipient int64, audio Audio, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendDocument sends general files. Documents must not exceed 50 MB in size.
func (b *Bot) sendDocument(recipient int64, document Document, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

//SendSticker sends stickers with .webp extensions.
func (b *Bot) sendSticker(recipient int64, sticker Sticker, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendVideo sends video files. Telegram clients support mp4 videos (other
// formats may be sent as Document). Video files must not exceed 50 MB in size.
func (b *Bot) sendVideo(recipient int64, video Video, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendVoice sends audio files, if you want Telegram clients to display
// the file as a playable voice message. For this to work, your audio must be
// in an .ogg file encoded with OPUS (other formats may be sent as Audio or
// Document). audio must not exceed 50 MB in size.
func (b *Bot) sendVoice(recipient int64, audio Audio, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendLocation sends location point on the map.
func (b *Bot) SendLocation(recipient int64, location Location, opts *SendOptions) (Message, error) {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(location.Long, 'f', -1, 64))

	mapSendOptions(&params, opts)

	var r struct {
		OK      bool    `json:"ok"`
		Desc    string  `json:"description"`
		ErrCode int     `json:"errorcode"`
		Message Message `json:"message"`
	}
	err := b.sendCommand(nil, "sendLocation", params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

// SendVenue sends information about a venue.
func (b *Bot) SendVenue(recipient int64, venue Venue, opts *SendOptions) (Message, error) {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(venue.Location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(venue.Location.Long, 'f', -1, 64))
	params.Set("title", venue.Title)
	params.Set("address", venue.Address)

	mapSendOptions(&params, opts)

	var r struct {
		OK      bool    `json:"ok"`
		Desc    string  `json:"description"`
		ErrCode int     `json:"errorcode"`
		Message Message `json:"message"`
	}
	err := b.sendCommand(nil, "sendVenue", params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

// SendChatAction broadcasts type of action to recipient, such as `typing`,
// `uploading a photo` etc.
func (b *Bot) SendChatAction(recipient int64, action Action) error {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("action", string(action))

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`
	}

	err := b.sendCommand(nil, "sendChatAction", params, &r)
	if err != nil {
		return err

	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

type SendOptions struct {
	ReplyTo int64

	ParseMode ParseMode

	DisableWebPagePreview bool

	DisableNotification bool

	ReplyMarkup ReplyMarkup
}

func (b *Bot) GetFile(fileID string) (File, error) {
	params := url.Values{}
	params.Set("file_id", fileID)

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
		File    File   `json:"result"`
	}
	err := b.sendCommand(nil, "getFile", params, &r)
	if err != nil {
		return File{}, err
	}

	if !r.OK {
		return File{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.File, nil
}

func (b *Bot) GetFileDownloadURL(fileID string) (string, error) {
	f, err := b.GetFile(fileID)
	if err != nil {
		return "", err
	}

	u := "https://api.telegram.org/file/bot" + b.token + "/" + f.FilePath
	return u, nil
}

func (b *Bot) sendCommand(ctx context.Context, method string, params url.Values, v interface{}) error {
	req, err := http.NewRequest("POST", b.baseURL+method, strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}

	if ctx == nil {
		ctx = context.Background()
	}

	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := b.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status: %v", resp.Status)
	}

	return json.NewDecoder(resp.Body).Decode(&v)
}

func (b *Bot) getMe() (User, error) {
	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`

		User User `json:"result"`
	}
	err := b.sendCommand(nil, "getMe", url.Values{}, &r)
	if err != nil {
		return User{}, err
	}

	if !r.OK {
		return User{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.User, nil
}

func mapSendOptions(m *url.Values, opts *SendOptions) {
	if opts == nil {
		return
	}

	if opts.ReplyTo != 0 {
		m.Set("reply_to_message_id", strconv.FormatInt(opts.ReplyTo, 10))
	}

	if opts.DisableWebPagePreview {
		m.Set("disable_web_page_preview", "true")
	}

	if opts.DisableNotification {
		m.Set("disable_notification", "true")
	}

	if opts.ParseMode != ModeNone {
		m.Set("parse_mode", string(opts.ParseMode))
	}

	// TODO: map ReplyMarkup options as well
}
<commit_msg>Check errors from sendCommand<commit_after>package telegram

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type ParseMode string

// Parse modes
const (
	ModeNone     ParseMode = ""
	ModeMarkdown ParseMode = "Markdown"
	ModeHTML     ParseMode = "HTML"
)

// Bot represent a Telegram bot.
type Bot struct {
	token     string
	baseURL   string
	client    *http.Client
	messageCh chan *Message
}

// New creates a new Telegram bot with the given token, which is given by
// Botfather. See https://core.telegram.org/bots#botfather
func New(token string) *Bot {
	return &Bot{
		token:     token,
		baseURL:   fmt.Sprintf("https://api.telegram.org/bot%v/", token),
		client:    &http.Client{Timeout: 30 * time.Second},
		messageCh: make(chan *Message),
	}
}

func (b *Bot) Handler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer w.WriteHeader(http.StatusOK)

		var u Update
		_ = json.NewDecoder(r.Body).Decode(&u)
		b.messageCh <- &u.Payload
	}
}

func (b *Bot) Messages() <-chan *Message {
	return b.messageCh
}

// SetWebhook assigns bot's webhook url with the given url.
func (b *Bot) SetWebhook(webhook string) error {
	params := url.Values{}
	params.Set("url", webhook)

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	err := b.sendCommand(nil, "setWebhook", params, &r)
	if err != nil {
		return err
	}

	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

// SendMessage sends text message to the recipient. Callers can send plain
// text or markdown messages by setting mode parameter.
func (b *Bot) SendMessage(recipient int64, message string, opts *SendOptions) (Message, error) {
	params := url.Values{
		"chat_id": {strconv.FormatInt(recipient, 10)},
		"text":    {message},
	}

	mapSendOptions(&params, opts)

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`
		Message Message
	}
	err := b.sendCommand(nil, "sendMessage", params, &r)
	if err != nil {
		return r.Message, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

func (b *Bot) forwardMessage(recipient User, message Message) (Message, error) {
	panic("not implemented yet")
}

// SendPhoto sends given photo to recipient. Only remote URLs are supported for now.
// A trivial example is:
//
//  b := bot.New("your-token-here")
//  photo := bot.Photo{URL: "http://i.imgur.com/6S9naG6.png"}
//  err := b.SendPhoto(recipient, photo, "sample image", nil)
func (b *Bot) SendPhoto(recipient int64, photo Photo, opts *SendOptions) (Message, error) {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("caption", photo.Caption)

	mapSendOptions(&params, opts)
	var r struct {
		OK      bool    `json:"ok"`
		Desc    string  `json:"description"`
		ErrCode int     `json:"error_code"`
		Message Message `json:"message"`
	}

	var err error
	if photo.Exists() {
		params.Set("photo", photo.FileID)
		err = b.sendCommand(nil, "sendPhoto", params, &r)
	} else if photo.URL != "" {
		params.Set("photo", photo.URL)
		err = b.sendCommand(nil, "sendPhoto", params, &r)
	} else {
		err = b.sendFile("sendPhoto", photo.File, "photo", params, &r)
	}

	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

func (b *Bot) sendFile(method string, f File, form string, params url.Values, v interface{}) error {
	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	part, err := w.CreateFormFile(form, f.Name)
	if err != nil {
		return err
	}

	_, err = io.Copy(part, f.Body)
	if err != nil {
		return err
	}

	for k, v := range params {
		w.WriteField(k, v[0])
	}

	err = w.Close()
	if err != nil {
		return err
	}

	resp, err := b.client.Post(b.baseURL+method, w.FormDataContentType(), &buf)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return json.NewDecoder(resp.Body).Decode(&v)
}

// SendAudio sends audio files, if you want Telegram clients to display
// them in the music player. audio must be in the .mp3 format and must not
// exceed 50 MB in size.
func (b *Bot) sendAudio(recipient int64, audio Audio, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendDocument sends general files. Documents must not exceed 50 MB in size.
func (b *Bot) sendDocument(recipient int64, document Document, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

//SendSticker sends stickers with .webp extensions.
func (b *Bot) sendSticker(recipient int64, sticker Sticker, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendVideo sends video files. Telegram clients support mp4 videos (other
// formats may be sent as Document). Video files must not exceed 50 MB in size.
func (b *Bot) sendVideo(recipient int64, video Video, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendVoice sends audio files, if you want Telegram clients to display
// the file as a playable voice message. For this to work, your audio must be
// in an .ogg file encoded with OPUS (other formats may be sent as Audio or
// Document). audio must not exceed 50 MB in size.
func (b *Bot) sendVoice(recipient int64, audio Audio, opts *SendOptions) (Message, error) {
	panic("not implemented yet")
}

// SendLocation sends location point on the map.
func (b *Bot) SendLocation(recipient int64, location Location, opts *SendOptions) (Message, error) {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(location.Long, 'f', -1, 64))

	mapSendOptions(&params, opts)

	var r struct {
		OK      bool    `json:"ok"`
		Desc    string  `json:"description"`
		ErrCode int     `json:"errorcode"`
		Message Message `json:"message"`
	}
	err := b.sendCommand(nil, "sendLocation", params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

// SendVenue sends information about a venue.
func (b *Bot) SendVenue(recipient int64, venue Venue, opts *SendOptions) (Message, error) {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(venue.Location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(venue.Location.Long, 'f', -1, 64))
	params.Set("title", venue.Title)
	params.Set("address", venue.Address)

	mapSendOptions(&params, opts)

	var r struct {
		OK      bool    `json:"ok"`
		Desc    string  `json:"description"`
		ErrCode int     `json:"errorcode"`
		Message Message `json:"message"`
	}
	err := b.sendCommand(nil, "sendVenue", params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

// SendChatAction broadcasts type of action to recipient, such as `typing`,
// `uploading a photo` etc.
func (b *Bot) SendChatAction(recipient int64, action Action) error {
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("action", string(action))

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`
	}

	err := b.sendCommand(nil, "sendChatAction", params, &r)
	if err != nil {
		return err

	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

type SendOptions struct {
	ReplyTo int64

	ParseMode ParseMode

	DisableWebPagePreview bool

	DisableNotification bool

	ReplyMarkup ReplyMarkup
}

func (b *Bot) GetFile(fileID string) (File, error) {
	params := url.Values{}
	params.Set("file_id", fileID)

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
		File    File   `json:"result"`
	}
	err := b.sendCommand(nil, "getFile", params, &r)
	if err != nil {
		return File{}, err
	}

	if !r.OK {
		return File{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.File, nil
}

func (b *Bot) GetFileDownloadURL(fileID string) (string, error) {
	f, err := b.GetFile(fileID)
	if err != nil {
		return "", err
	}

	u := "https://api.telegram.org/file/bot" + b.token + "/" + f.FilePath
	return u, nil
}

func (b *Bot) sendCommand(ctx context.Context, method string, params url.Values, v interface{}) error {
	req, err := http.NewRequest("POST", b.baseURL+method, strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}

	if ctx == nil {
		ctx = context.Background()
	}

	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := b.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return json.NewDecoder(resp.Body).Decode(&v)
}

func (b *Bot) getMe() (User, error) {
	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`

		User User `json:"result"`
	}
	err := b.sendCommand(nil, "getMe", url.Values{}, &r)
	if err != nil {
		return User{}, err
	}

	if !r.OK {
		return User{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.User, nil
}

func mapSendOptions(m *url.Values, opts *SendOptions) {
	if opts == nil {
		return
	}

	if opts.ReplyTo != 0 {
		m.Set("reply_to_message_id", strconv.FormatInt(opts.ReplyTo, 10))
	}

	if opts.DisableWebPagePreview {
		m.Set("disable_web_page_preview", "true")
	}

	if opts.DisableNotification {
		m.Set("disable_notification", "true")
	}

	if opts.ParseMode != ModeNone {
		m.Set("parse_mode", string(opts.ParseMode))
	}

	// TODO: map ReplyMarkup options as well
}
<|endoftext|>


<commit_before>package scroll

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/mux"
	"github.com/mailgun/log"
	"github.com/mailgun/manners"
	"github.com/mailgun/metrics"

	"github.com/mailgun/scroll/vulcan"
	"github.com/mailgun/scroll/vulcan/middleware"
)

const (
	// Suggested result set limit for APIs that may return many entries (e.g. paging).
	DefaultLimit int = 100

	// Suggested max allowed result set limit for APIs that may return many entries (e.g. paging).
	MaxLimit int = 10000

	// Suggested max allowed amount of entries that batch APIs can accept (e.g. batch uploads).
	MaxBatchSize int = 1000
)

// Represents an app.
type App struct {
	Config   AppConfig
	router   *mux.Router
	registry *vulcan.Registry
	stats    *appStats
}

// Represents a configuration object an app is created with.
type AppConfig struct {
	// name of the app being created
	Name string

	// IP/port the app will bind to
	ListenIP   string
	ListenPort int

	// optional router to use
	Router *mux.Router

	// hostnames of the public and protected API entrypoints used for vulcan registration
	PublicAPIHost    string
	ProtectedAPIHost string

	// whether to register the app's endpoint and handlers in vulcan
	Register bool

	// metrics service used for emitting the app's real-time metrics
	Client metrics.Client
}

// Create a new app.
func NewApp() *App {
	return NewAppWithConfig(AppConfig{})
}

// Create a new app with the provided configuration.
func NewAppWithConfig(config AppConfig) *App {
	var reg *vulcan.Registry
	if config.Register != false {
		reg = vulcan.NewRegistry(vulcan.Config{
			PublicAPIHost:    config.PublicAPIHost,
			ProtectedAPIHost: config.ProtectedAPIHost,
		})
	}

	router := config.Router
	if router == nil {
		router = mux.NewRouter()
	}

	return &App{
		Config:   config,
		router:   router,
		registry: reg,
		stats:    newAppStats(config.Client),
	}
}

// Register a handler function.
//
// If vulcan registration is enabled in the both app config and handler spec,
// the handler will be registered in the local etcd instance.
func (app *App) AddHandler(spec Spec) error {
	var handler http.HandlerFunc

	// make a handler depending on the function provided in the spec
	if spec.RawHandler != nil {
		handler = spec.RawHandler
	} else if spec.Handler != nil {
		handler = MakeHandler(app, spec.Handler, spec)
	} else if spec.HandlerWithBody != nil {
		handler = MakeHandlerWithBody(app, spec.HandlerWithBody, spec)
	} else {
		return fmt.Errorf("the spec does not provide a handler function: %v", spec)
	}

	// register the handler in the router
	route := app.router.HandleFunc(spec.Path, handler).Methods(spec.Methods...)
	if len(spec.Headers) != 0 {
		route.Headers(spec.Headers...)
	}

	// vulcan registration
	if app.registry != nil && spec.Register != false {
		app.registerLocation(spec.Methods, spec.Path, spec.Scopes, spec.Middlewares)
	}

	return nil
}

// GetHandler returns HTTP compatible Handler interface.
func (app *App) GetHandler() http.Handler {
	return app.router
}

// SetNotFoundHandler sets the handler for the case when URL can not be matched by the router.
func (app *App) SetNotFoundHandler(fn http.HandlerFunc) {
	app.router.NotFoundHandler = fn
}

// Start the app on the configured host/port.
//
// If vulcan registration is enabled in the app config, starts a goroutine that
// will be registering the app's endpoint once every minute in the local etcd
// instance.
//
// Supports graceful shutdown on 'kill' and 'int' signals.
func (app *App) Run() error {
	http.Handle("/", app.router)

	if app.registry != nil {
		go func() {
			for {
				app.registerEndpoint()
				time.Sleep(60 * time.Second)
			}
		}()
	}

	// listen for a shutdown signal
	go func() {
		exitChan := make(chan os.Signal, 1)
		signal.Notify(exitChan, os.Interrupt, os.Kill)
		s := <-exitChan
		log.Infof("Got shutdown signal: %v", s)
		manners.Close()
	}()

	return manners.ListenAndServe(
		fmt.Sprintf("%v:%v", app.Config.ListenIP, app.Config.ListenPort), nil)
}

// registerEndpoint is a helper for registering the app's endpoint in vulcan.
func (app *App) registerEndpoint() {
	endpoint, err := vulcan.NewEndpoint(app.Config.Name, app.Config.ListenIP, app.Config.ListenPort)
	if err != nil {
		log.Errorf("Failed to create an endpoint: %v", err)
		return
	}

	if err := app.registry.RegisterEndpoint(endpoint); err != nil {
		log.Errorf("Failed to register an endpoint: %v %v", endpoint, err)
		return
	}

	log.Infof("Registered: %v", endpoint)
}

// registerLocation is a helper for registering handlers in vulcan.
func (app *App) registerLocation(methods []string, path string, scopes []Scope, middlewares []middleware.Middleware) {
	for _, scope := range scopes {
		app.registerLocationForScope(methods, path, scope, middlewares)
	}
}

// registerLocationForScope registers a location with a specified scope.
func (app *App) registerLocationForScope(methods []string, path string, scope Scope, middlewares []middleware.Middleware) {
	host, err := app.apiHostForScope(scope)
	if err != nil {
		log.Errorf("Failed to register a location: %v", err)
		return
	}
	app.registerLocationForHost(methods, path, host, middlewares)
}

// registerLocationForHost registers a location for a specified hostname.
func (app *App) registerLocationForHost(methods []string, path, host string, middlewares []middleware.Middleware) {
	location := vulcan.NewLocation(host, methods, path, app.Config.Name, middlewares)

	if err := app.registry.RegisterLocation(location); err != nil {
		log.Errorf("Failed to register a location: %v %v", location, err)
		return
	}

	log.Infof("Registered: %v", location)
}

// apiHostForScope is a helper that returns an appropriate API hostname for a provided scope.
func (app *App) apiHostForScope(scope Scope) (string, error) {
	if scope == ScopePublic {
		return app.Config.PublicAPIHost, nil
	} else if scope == ScopeProtected {
		return app.Config.ProtectedAPIHost, nil
	} else {
		return "", fmt.Errorf("unknown scope value: %v", scope)
	}
}
<commit_msg>Added protected API URL.<commit_after>package scroll

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/mux"
	"github.com/mailgun/log"
	"github.com/mailgun/manners"
	"github.com/mailgun/metrics"

	"github.com/mailgun/scroll/vulcan"
	"github.com/mailgun/scroll/vulcan/middleware"
)

const (
	// Suggested result set limit for APIs that may return many entries (e.g. paging).
	DefaultLimit int = 100

	// Suggested max allowed result set limit for APIs that may return many entries (e.g. paging).
	MaxLimit int = 10000

	// Suggested max allowed amount of entries that batch APIs can accept (e.g. batch uploads).
	MaxBatchSize int = 1000
)

// Represents an app.
type App struct {
	Config   AppConfig
	router   *mux.Router
	registry *vulcan.Registry
	stats    *appStats
}

// Represents a configuration object an app is created with.
type AppConfig struct {
	// name of the app being created
	Name string

	// IP/port the app will bind to
	ListenIP   string
	ListenPort int

	// optional router to use
	Router *mux.Router

	// hostnames of the public and protected API entrypoints used for vulcan registration
	PublicAPIHost    string
	ProtectedAPIHost string
	ProtectedAPIURL  string

	// whether to register the app's endpoint and handlers in vulcan
	Register bool

	// metrics service used for emitting the app's real-time metrics
	Client metrics.Client
}

// Create a new app.
func NewApp() *App {
	return NewAppWithConfig(AppConfig{})
}

// Create a new app with the provided configuration.
func NewAppWithConfig(config AppConfig) *App {
	var reg *vulcan.Registry
	if config.Register != false {
		reg = vulcan.NewRegistry(vulcan.Config{
			PublicAPIHost:    config.PublicAPIHost,
			ProtectedAPIHost: config.ProtectedAPIHost,
		})
	}

	router := config.Router
	if router == nil {
		router = mux.NewRouter()
	}

	return &App{
		Config:   config,
		router:   router,
		registry: reg,
		stats:    newAppStats(config.Client),
	}
}

// Register a handler function.
//
// If vulcan registration is enabled in the both app config and handler spec,
// the handler will be registered in the local etcd instance.
func (app *App) AddHandler(spec Spec) error {
	var handler http.HandlerFunc

	// make a handler depending on the function provided in the spec
	if spec.RawHandler != nil {
		handler = spec.RawHandler
	} else if spec.Handler != nil {
		handler = MakeHandler(app, spec.Handler, spec)
	} else if spec.HandlerWithBody != nil {
		handler = MakeHandlerWithBody(app, spec.HandlerWithBody, spec)
	} else {
		return fmt.Errorf("the spec does not provide a handler function: %v", spec)
	}

	// register the handler in the router
	route := app.router.HandleFunc(spec.Path, handler).Methods(spec.Methods...)
	if len(spec.Headers) != 0 {
		route.Headers(spec.Headers...)
	}

	// vulcan registration
	if app.registry != nil && spec.Register != false {
		app.registerLocation(spec.Methods, spec.Path, spec.Scopes, spec.Middlewares)
	}

	return nil
}

// GetHandler returns HTTP compatible Handler interface.
func (app *App) GetHandler() http.Handler {
	return app.router
}

// SetNotFoundHandler sets the handler for the case when URL can not be matched by the router.
func (app *App) SetNotFoundHandler(fn http.HandlerFunc) {
	app.router.NotFoundHandler = fn
}

// Start the app on the configured host/port.
//
// If vulcan registration is enabled in the app config, starts a goroutine that
// will be registering the app's endpoint once every minute in the local etcd
// instance.
//
// Supports graceful shutdown on 'kill' and 'int' signals.
func (app *App) Run() error {
	http.Handle("/", app.router)

	if app.registry != nil {
		go func() {
			for {
				app.registerEndpoint()
				time.Sleep(60 * time.Second)
			}
		}()
	}

	// listen for a shutdown signal
	go func() {
		exitChan := make(chan os.Signal, 1)
		signal.Notify(exitChan, os.Interrupt, os.Kill)
		s := <-exitChan
		log.Infof("Got shutdown signal: %v", s)
		manners.Close()
	}()

	return manners.ListenAndServe(
		fmt.Sprintf("%v:%v", app.Config.ListenIP, app.Config.ListenPort), nil)
}

// registerEndpoint is a helper for registering the app's endpoint in vulcan.
func (app *App) registerEndpoint() {
	endpoint, err := vulcan.NewEndpoint(app.Config.Name, app.Config.ListenIP, app.Config.ListenPort)
	if err != nil {
		log.Errorf("Failed to create an endpoint: %v", err)
		return
	}

	if err := app.registry.RegisterEndpoint(endpoint); err != nil {
		log.Errorf("Failed to register an endpoint: %v %v", endpoint, err)
		return
	}

	log.Infof("Registered: %v", endpoint)
}

// registerLocation is a helper for registering handlers in vulcan.
func (app *App) registerLocation(methods []string, path string, scopes []Scope, middlewares []middleware.Middleware) {
	for _, scope := range scopes {
		app.registerLocationForScope(methods, path, scope, middlewares)
	}
}

// registerLocationForScope registers a location with a specified scope.
func (app *App) registerLocationForScope(methods []string, path string, scope Scope, middlewares []middleware.Middleware) {
	host, err := app.apiHostForScope(scope)
	if err != nil {
		log.Errorf("Failed to register a location: %v", err)
		return
	}
	app.registerLocationForHost(methods, path, host, middlewares)
}

// registerLocationForHost registers a location for a specified hostname.
func (app *App) registerLocationForHost(methods []string, path, host string, middlewares []middleware.Middleware) {
	location := vulcan.NewLocation(host, methods, path, app.Config.Name, middlewares)

	if err := app.registry.RegisterLocation(location); err != nil {
		log.Errorf("Failed to register a location: %v %v", location, err)
		return
	}

	log.Infof("Registered: %v", location)
}

// apiHostForScope is a helper that returns an appropriate API hostname for a provided scope.
func (app *App) apiHostForScope(scope Scope) (string, error) {
	if scope == ScopePublic {
		return app.Config.PublicAPIHost, nil
	} else if scope == ScopeProtected {
		return app.Config.ProtectedAPIHost, nil
	} else {
		return "", fmt.Errorf("unknown scope value: %v", scope)
	}
}
<|endoftext|>


<commit_before>package app

import (
	"bytes"
	"image"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"strings"

	_ "image/jpeg"
	_ "image/png"

	fb "github.com/huandu/facebook"

	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
)

var clientID = "526791527487217"
var FbApp = fb.New(clientID, APPSECRET)

var aboutParams = fb.Params{
	"method":       fb.GET,
	"relative_url": "me?fields=name,email,gender,age_range,address,location",
}

var photoParams = fb.Params{
	"method":       fb.GET,
	"relative_url": "me/picture?width=320&height=320&redirect=false",
}

func init() {
	http.HandleFunc("/static/", StaticHandler)
	http.HandleFunc("/", MainHandler)
	http.HandleFunc("/web/", WebHandler)
	http.HandleFunc("/mobile/", MobileHandler)

	fb.Debug = fb.DEBUG_ALL
	FbApp.EnableAppsecretProof = false
}

func WebHandler(w http.ResponseWriter, r *http.Request) {
	paths := strings.Split(r.URL.Path, "/")
	party := paths[len(paths)-1]
	query := r.URL.Query()
	code := query.Get("code")

	if code == "" {
		http.Error(w, "Cannot get code from facebook.", 505)
		return
	}

	context := appengine.NewContext(r)
	client := urlfetch.Client(context)
	fb.SetHttpClient(client)

	redirectURL := "http://" + r.Host + r.URL.Path
	accessResp, err := fb.Get("/v2.4/oauth/access_token", fb.Params{
		"code":          code,
		"redirect_uri":  redirectURL,
		"client_id":     clientID,
		"client_secret": APPSECRET,
	})
	check(err, context)

	var accessToken string
	accessResp.DecodeField("access_token", &accessToken)

	photoID := UploadPhoto(accessToken, party, context)
	redirectUrl	:= "https://facebook.com/photo.php?fbid=" + photoID + "&makeprofile=1&prof"
	http.Redirect(w, r, redirectUrl, 303)
}

func MobileHandler(w http.ResponseWriter, r *http.Request) {
	paths := strings.Split(r.URL.Path, "/")
	party := paths[len(paths)-1]
	query := r.URL.Query()
	accessToken := query.Get("access_token")
	context := appengine.NewContext(r)

	photoID := UploadPhoto(accessToken, party, context)
	w.Write([]byte(photoID))
}

func UploadPhoto(accessToken string, party string, context appengine.Context) string {
	session := FbApp.Session(accessToken)
	session.HttpClient = urlfetch.Client(context)
	err := session.Validate()
	check(err, context)

	results, err := session.BatchApi(aboutParams, photoParams)
	check(err, context)

	aboutBatch, err := results[0].Batch()
	check(err, context)
	photoBatch, err := results[1].Batch()
	check(err, context)

	aboutResp := aboutBatch.Result
	photoResp := photoBatch.Result

	SaveAboutUser(&aboutResp, context)

	profilePicture := GetUserPhoto(&photoResp, context)

	imagebytes := addLogo(profilePicture, party, context)
	form, mime := CreateImageForm(&imagebytes, context)

	url := "https://graph.facebook.com/v2.4/me/photos" +
		"?access_token=" + accessToken +
		"&appsecret_proof=" + session.AppsecretProof()

	uploadResquest, _ := http.NewRequest("POST", url, form)
	uploadResquest.Header.Set("Content-Type", mime)
	uploadResp, _ := session.Request(uploadResquest)
	check(err, context)

	var photoID string
	uploadResp.DecodeField("id", &photoID)

	return photoID
}

func StaticHandler(w http.ResponseWriter, r *http.Request) {
	path := "." + r.URL.Path

	if f, err := os.Stat(path); err == nil && !f.IsDir() {
		http.ServeFile(w, r, path)
		return
	}

	http.NotFound(w, r)
}

func MainHandler(w http.ResponseWriter, r *http.Request) {
	switch {

	case r.URL.Path == "/privacy":
		context := appengine.NewContext(r)
		data, err := ioutil.ReadFile("privacy.html")
		check(err, context)
		w.Write(data)

	case r.URL.Path != "/":
		http.NotFound(w, r)

	default:
		handleMain(w, r)
	}

	return
}

func handleMain(w http.ResponseWriter, r *http.Request) {
	context := appengine.NewContext(r)
	data, err := ioutil.ReadFile("index.html")
	check(err, context)
	w.Write(data)
}

type Log struct {
	Name     string
	Gender   string
	Party    string
	Email    string
	AgeRange string
	Location string
}

func SaveAboutUser(aboutResp *fb.Result, context appengine.Context) {
	var log Log
	aboutResp.Decode(&log)

	var ageRange, location map[string]string
	aboutResp.DecodeField("location", &location)
	aboutResp.DecodeField("age_range", &ageRange)

	log.AgeRange = ageRange["min"]
	log.Location = location["name"]

	_, err := datastore.Put(context,
		datastore.NewIncompleteKey(context, "log", nil),
		&log)
	check(err, context)
}

func GetUserPhoto(photoResp *fb.Result, context appengine.Context) *image.Image {
     	var dataField fb.Result
	photoResp.DecodeField("data", &dataField)

	var url string
	dataField.DecodeField("url", &url)

	client := urlfetch.Client(context)
	resp, err := client.Get(url)
	data, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	check(err, context)

	reader := bytes.NewReader(data)
	profilePicture, _, err := image.Decode(reader)
	check(err, context)

	return &profilePicture
}

func CreateImageForm(imageBytes *[]byte, context appengine.Context) (*bytes.Buffer, string) {
	var formBuffer bytes.Buffer
	multiWriter := multipart.NewWriter(&formBuffer)

	imageField, err := multiWriter.CreateFormFile("source", "election.png")
	check(err, context)

	imageBuffer := bytes.NewBuffer(*imageBytes)
	_, err = io.Copy(imageField, imageBuffer)
	check(err, context)

	multiWriter.Close()
	return &formBuffer, multiWriter.FormDataContentType()
}
<commit_msg>fixed random profile color<commit_after>package app

import (
	"bytes"
	"image"
	"io"
	"io/ioutil"
	"math/rand"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
	"time"

	_ "image/jpeg"
	_ "image/png"

	fb "github.com/huandu/facebook"

	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
)

var clientID = "526791527487217"
var FbApp = fb.New(clientID, APPSECRET)

var aboutParams = fb.Params{
	"method":       fb.GET,
	"relative_url": "me?fields=name,email,gender,age_range,address,location",
}

var photoParams = fb.Params{
	"method":       fb.GET,
	"relative_url": "me/picture?width=320&height=320&redirect=false",
}

func init() {
	http.HandleFunc("/static/", StaticHandler)
	http.HandleFunc("/", MainHandler)
	http.HandleFunc("/web/", WebHandler)
	http.HandleFunc("/mobile/", MobileHandler)

	fb.Debug = fb.DEBUG_ALL
	FbApp.EnableAppsecretProof = true

	rand.Seed(time.Now().UTC().UnixNano())
}

func WebHandler(w http.ResponseWriter, r *http.Request) {
	paths := strings.Split(r.URL.Path, "/")
	party := paths[len(paths)-1]
	query := r.URL.Query()
	code := query.Get("code")

	if code == "" {
		http.Error(w, "Cannot get code from facebook.", 505)
		return
	}

	context := appengine.NewContext(r)
	client := urlfetch.Client(context)
	fb.SetHttpClient(client)

	redirectURL := "http://" + r.Host + r.URL.Path
	accessResp, err := fb.Get("/v2.4/oauth/access_token", fb.Params{
		"code":          code,
		"redirect_uri":  redirectURL,
		"client_id":     clientID,
		"client_secret": APPSECRET,
	})
	check(err, context)

	var accessToken string
	accessResp.DecodeField("access_token", &accessToken)

	photoID := UploadPhoto(accessToken, party, context)
	redirectUrl := "https://facebook.com/photo.php?fbid=" + photoID + "&makeprofile=1&prof"
	http.Redirect(w, r, redirectUrl, 303)
}

func MobileHandler(w http.ResponseWriter, r *http.Request) {
	paths := strings.Split(r.URL.Path, "/")
	party := paths[len(paths)-1]
	query := r.URL.Query()
	accessToken := query.Get("access_token")
	context := appengine.NewContext(r)

	photoID := UploadPhoto(accessToken, party, context)
	w.Write([]byte(photoID))
}

func UploadPhoto(accessToken string, party string, context appengine.Context) string {
	session := FbApp.Session(accessToken)
	session.HttpClient = urlfetch.Client(context)
	err := session.Validate()
	check(err, context)

	results, err := session.BatchApi(aboutParams, photoParams)
	check(err, context)

	aboutBatch, err := results[0].Batch()
	check(err, context)
	photoBatch, err := results[1].Batch()
	check(err, context)

	aboutResp := aboutBatch.Result
	photoResp := photoBatch.Result

	SaveAboutUser(&aboutResp, context)

	profilePicture := GetUserPhoto(&photoResp, context)

	imagebytes := addLogo(profilePicture, party, context)
	form, mime := CreateImageForm(&imagebytes, context)

	url := "https://graph.facebook.com/v2.4/me/photos" +
		"?access_token=" + accessToken +
		"&appsecret_proof=" + session.AppsecretProof()

	uploadResquest, _ := http.NewRequest("POST", url, form)
	uploadResquest.Header.Set("Content-Type", mime)
	uploadResp, _ := session.Request(uploadResquest)
	check(err, context)

	var photoID string
	uploadResp.DecodeField("id", &photoID)

	return photoID
}

func StaticHandler(w http.ResponseWriter, r *http.Request) {
	path := "." + r.URL.Path

	if f, err := os.Stat(path); err == nil && !f.IsDir() {
		http.ServeFile(w, r, path)
		return
	}

	http.NotFound(w, r)
}

func MainHandler(w http.ResponseWriter, r *http.Request) {
	switch {

	case r.URL.Path == "/privacy":
		context := appengine.NewContext(r)
		data, err := ioutil.ReadFile("privacy.html")
		check(err, context)
		w.Write(data)

	case r.URL.Path != "/":
		http.NotFound(w, r)

	default:
		handleMain(w, r)
	}

	return
}

func handleMain(w http.ResponseWriter, r *http.Request) {
	context := appengine.NewContext(r)
	data, err := ioutil.ReadFile("index.html")
	check(err, context)
	w.Write(data)
}

type Log struct {
	Name     string
	Gender   string
	Party    string
	Email    string
	AgeRange string
	Location string
}

func SaveAboutUser(aboutResp *fb.Result, context appengine.Context) {
	var log Log
	aboutResp.Decode(&log)

	var ageRange, location map[string]string
	aboutResp.DecodeField("location", &location)
	aboutResp.DecodeField("age_range", &ageRange)

	log.AgeRange = ageRange["min"]
	log.Location = location["name"]

	_, err := datastore.Put(context,
		datastore.NewIncompleteKey(context, "log", nil),
		&log)
	check(err, context)
}

func GetUserPhoto(photoResp *fb.Result, context appengine.Context) *image.Image {
	var dataField fb.Result
	photoResp.DecodeField("data", &dataField)

	var url string
	dataField.DecodeField("url", &url)

	client := urlfetch.Client(context)
	resp, err := client.Get(url)
	data, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	check(err, context)

	reader := bytes.NewReader(data)
	profilePicture, _, err := image.Decode(reader)
	check(err, context)

	return &profilePicture
}

func CreateImageForm(imageBytes *[]byte, context appengine.Context) (*bytes.Buffer, string) {
	var formBuffer bytes.Buffer
	multiWriter := multipart.NewWriter(&formBuffer)

	imageField, err := multiWriter.CreateFormFile("source", "election.png")
	check(err, context)

	imageBuffer := bytes.NewBuffer(*imageBytes)
	_, err = io.Copy(imageField, imageBuffer)
	check(err, context)

	multiWriter.Close()
	return &formBuffer, multiWriter.FormDataContentType()
}
<|endoftext|>


<commit_before>// Copyright 2015 The Go 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 sensors

import (
	"errors"
	"sync"
)

var (
	muAStarted sync.Mutex
	aStarted   = false
)

// StartAccelerometer starts the accelerometer.
// Once the accelerometer is no longer in use, it should be stopped
// by calling StopAccelerometer.
func StartAccelerometer(samplesPerSec int) error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if aStarted {
		return errors.New("sensors: accelerometer already started")
	}
	if err := startAccelerometer(samplesPerSec); err != nil {
		return err
	}
	aStarted = true
	return nil
}

// PollAccelerometer polls a new event for the accelerometer event queue.
// It blocks until an event is available.
// A call to StartAccelerometer is mandatory to start the accelerometer
// sensor and initialize its event queue.
// You have to call PollAccelerometer from the same OS thread that the
// accelerometer is started on.
func PollAccelerometer() (deltaX, deltaY, deltaZ float64) {
	return pollAccelerometer()
}

// StopAccelerometer stops the accelerometer and frees the related resources.
func StopAccelerometer() error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if !aStarted {
		return errors.New("sensors: accelerometer hasn't been started")
	}
	if err := stopAccelerometer(); err != nil {
		return err
	}
	aStarted = false
	return nil
}

func StartGyroscope(fn func(roll, pitch, yaw float64)) error {
	panic("not yet implemented")
}

func StopGyroscope() {
	panic("not yet implemented")
}

func StartMagnetometer(fn func(azimut, pitch, roll float64)) error {
	panic("not yet implemented")
}

func StopMagnetometer() {
	panic("not yet implemented")
}

// Type of the network that is currently in use.
const (
	TypeWiFi = iota
	TypeMobile
	TypeOther
)

// Connectivity status.
const (
	StatusUnknown = iota
	StatusConnecting
	StatusConnected
	StatusDisconnecting
)

// Connectivity returns the type and the status of the network that is
// currently in use.
func Connectivity() (networkType int, status int) {
	panic("not yet implemented")
}
<commit_msg>switch to sync apis<commit_after>// Copyright 2015 The Go 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 sensors

import (
	"errors"
	"sync"
)

var (
	muAStarted sync.Mutex
	aStarted   = false
)

// StartAccelerometer starts the accelerometer.
// Once the accelerometer is no longer in use, it should be stopped
// by calling StopAccelerometer.
func StartAccelerometer(samplesPerSec int) error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if aStarted {
		return errors.New("sensors: accelerometer already started")
	}
	if err := startAccelerometer(samplesPerSec); err != nil {
		return err
	}
	aStarted = true
	return nil
}

// PollAccelerometer polls a new event for the accelerometer event queue.
// It blocks until an event is available.
// A call to StartAccelerometer is mandatory to start the accelerometer
// sensor and initialize its event queue.
// You have to call PollAccelerometer from the same OS thread that the
// accelerometer is started on.
func PollAccelerometer() (deltaX, deltaY, deltaZ float64) {
	return pollAccelerometer()
}

// StopAccelerometer stops the accelerometer and frees the related resources.
func StopAccelerometer() error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if !aStarted {
		return errors.New("sensors: accelerometer hasn't been started")
	}
	if err := stopAccelerometer(); err != nil {
		return err
	}
	aStarted = false
	return nil
}

func StartGyroscope(samplesPerSec int) error {
	panic("not yet implemented")
}

func PollGyroscope() (roll, pitch, yaw float64) {
	panic("not yet implemented")
}

func StopGyroscope() error {
	panic("not yet implemented")
}

func StartMagnetometer(samplesPerSec int) error {
	panic("not yet implemented")
}

func PollMagnetometer(azimut, pitch, roll float64) {
	panic("not yet implemented")
}

func StopMagnetometer() error {
	panic("not yet implemented")
}

// Type of the network that is currently in use.
const (
	TypeWiFi = iota
	TypeMobile
	TypeOther
)

// Connectivity status.
const (
	StatusUnknown = iota
	StatusConnecting
	StatusConnected
	StatusDisconnecting
)

// Connectivity returns the type and the status of the network that is
// currently in use.
func Connectivity() (networkType int, status int) {
	panic("not yet implemented")
}
<|endoftext|>


<commit_before>// +build !windows
// This file contains a simple and incomplete implementation of the terminfo
// database. Information was taken from the ncurses manpages term(5) and
// terminfo(5). Currently, only the string capabilities for special keys and for
// functions without parameters are actually used. Colors are still done with
// ANSI escape sequences. Other special features that are not (yet?) supported
// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database
// format and extended capabilities.

package termbox

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

const (
	ti_magic         = 0432
	ti_header_length = 12
)

func load_terminfo() ([]byte, error) {
	var data []byte
	var err error

	term := os.Getenv("TERM")
	if term == "" {
		return nil, fmt.Errorf("termbox: TERM not set")
	}

	// The following behaviour follows the one described in terminfo(5) as
	// distributed by ncurses.

	terminfo := os.Getenv("TERMINFO")
	if terminfo != "" {
		// if TERMINFO is set, no other directory should be searched
		return ti_try_path(terminfo)
	}

	// next, consider ~/.terminfo
	home := os.Getenv("HOME")
	if home != "" {
		data, err = ti_try_path(home + "/.terminfo")
		if err == nil {
			return data, nil
		}
	}

	// next, TERMINFO_DIRS
	dirs := os.Getenv("TERMINFO_DIRS")
	if dirs != "" {
		for _, dir := range strings.Split(dirs, ":") {
			if dir == "" {
				// "" -> "/usr/share/terminfo"
				dir = "/usr/share/terminfo"
			}
			data, err = ti_try_path(dir)
			if err == nil {
				return data, nil
			}
		}
	}

	// fall back to /usr/share/terminfo
	return ti_try_path("/usr/share/terminfo")
}

func ti_try_path(path string) (data []byte, err error) {
	// load_terminfo already made sure it is set
	term := os.Getenv("TERM")

	// first try, the typical *nix path
	terminfo := path + "/" + term[0:1] + "/" + term
	data, err = ioutil.ReadFile(terminfo)
	if err == nil {
		return
	}

	// fallback to darwin specific dirs structure
	terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term
	data, err = ioutil.ReadFile(terminfo)
	return
}

func setup_term_builtin() error {
	name := os.Getenv("TERM")
	if name == "" {
		return errors.New("termbox: TERM environment variable not set")
	}

	for _, t := range terms {
		if t.name == name {
			keys = t.keys
			funcs = t.funcs
			return nil
		}
	}

	compat_table := []struct {
		partial string
		keys    []string
		funcs   []string
	}{
		{"xterm", xterm_keys, xterm_funcs},
		{"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs},
		{"linux", linux_keys, linux_funcs},
		{"Eterm", eterm_keys, eterm_funcs},
		{"screen", screen_keys, screen_funcs},
		// let's assume that 'cygwin' is xterm compatible
		{"cygwin", xterm_keys, xterm_funcs},
	}

	// try compatibility variants
	for _, it := range compat_table {
		if strings.Contains(name, it.partial) {
			keys = it.keys
			funcs = it.funcs
			return nil
		}
	}

	return errors.New("termbox: unsupported terminal")
}

func setup_term() (err error) {
	var data []byte
	var header [6]int16
	var str_offset, table_offset int16

	data, err = load_terminfo()
	if err != nil {
		return setup_term_builtin()
	}

	rd := bytes.NewReader(data)
	// 0: magic number, 1: size of names section, 2: size of boolean section, 3:
	// size of numbers section (in integers), 4: size of the strings section (in
	// integers), 5: size of the string table

	err = binary.Read(rd, binary.LittleEndian, header[:])
	if err != nil {
		return
	}

	if (header[1]+header[2])%2 != 0 {
		// old quirk to align everything on word boundaries
		header[2] += 1
	}
	str_offset = ti_header_length + header[1] + header[2] + 2*header[3]
	table_offset = str_offset + 2*header[4]

	keys = make([]string, 0xFFFF-key_min)
	for i, _ := range keys {
		keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset)
		if err != nil {
			return
		}
	}
	funcs = make([]string, t_max_funcs)
	// the last two entries are reserved for mouse. because the table offset is
	// not there, the two entries have to fill in manually
	for i, _ := range funcs[:len(funcs)-2] {
		funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset)
		if err != nil {
			return
		}
	}
	funcs[t_max_funcs-2] = "\x1b[?1000h"
	funcs[t_max_funcs-1] = "\x1b[?1000l"
	return nil
}

func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) {
	var off int16

	_, err := rd.Seek(int64(str_off), 0)
	if err != nil {
		return "", err
	}
	err = binary.Read(rd, binary.LittleEndian, &off)
	if err != nil {
		return "", err
	}
	_, err = rd.Seek(int64(table+off), 0)
	if err != nil {
		return "", err
	}
	var bs []byte
	for {
		b, err := rd.ReadByte()
		if err != nil {
			return "", err
		}
		if b == byte(0x00) {
			break
		}
		bs = append(bs, b)
	}
	return string(bs), nil
}

// "Maps" the function constants from termbox.go to the number of the respective
// string capability in the terminfo file. Taken from (ncurses) term.h.
var ti_funcs = []int16{
	28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
}

// Same as above for the special keys.
var ti_keys = []int16{
	66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70,
	71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83,
}
<commit_msg>Added support for st<commit_after>// +build !windows
// This file contains a simple and incomplete implementation of the terminfo
// database. Information was taken from the ncurses manpages term(5) and
// terminfo(5). Currently, only the string capabilities for special keys and for
// functions without parameters are actually used. Colors are still done with
// ANSI escape sequences. Other special features that are not (yet?) supported
// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database
// format and extended capabilities.

package termbox

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

const (
	ti_magic         = 0432
	ti_header_length = 12
)

func load_terminfo() ([]byte, error) {
	var data []byte
	var err error

	term := os.Getenv("TERM")
	if term == "" {
		return nil, fmt.Errorf("termbox: TERM not set")
	}

	// The following behaviour follows the one described in terminfo(5) as
	// distributed by ncurses.

	terminfo := os.Getenv("TERMINFO")
	if terminfo != "" {
		// if TERMINFO is set, no other directory should be searched
		return ti_try_path(terminfo)
	}

	// next, consider ~/.terminfo
	home := os.Getenv("HOME")
	if home != "" {
		data, err = ti_try_path(home + "/.terminfo")
		if err == nil {
			return data, nil
		}
	}

	// next, TERMINFO_DIRS
	dirs := os.Getenv("TERMINFO_DIRS")
	if dirs != "" {
		for _, dir := range strings.Split(dirs, ":") {
			if dir == "" {
				// "" -> "/usr/share/terminfo"
				dir = "/usr/share/terminfo"
			}
			data, err = ti_try_path(dir)
			if err == nil {
				return data, nil
			}
		}
	}

	// fall back to /usr/share/terminfo
	return ti_try_path("/usr/share/terminfo")
}

func ti_try_path(path string) (data []byte, err error) {
	// load_terminfo already made sure it is set
	term := os.Getenv("TERM")

	// first try, the typical *nix path
	terminfo := path + "/" + term[0:1] + "/" + term
	data, err = ioutil.ReadFile(terminfo)
	if err == nil {
		return
	}

	// fallback to darwin specific dirs structure
	terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term
	data, err = ioutil.ReadFile(terminfo)
	return
}

func setup_term_builtin() error {
	name := os.Getenv("TERM")
	if name == "" {
		return errors.New("termbox: TERM environment variable not set")
	}

	for _, t := range terms {
		if t.name == name {
			keys = t.keys
			funcs = t.funcs
			return nil
		}
	}

	compat_table := []struct {
		partial string
		keys    []string
		funcs   []string
	}{
		{"xterm", xterm_keys, xterm_funcs},
		{"st-256color", xterm_keys, xterm_funcs},
		{"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs},
		{"linux", linux_keys, linux_funcs},
		{"Eterm", eterm_keys, eterm_funcs},
		{"screen", screen_keys, screen_funcs},
		// let's assume that 'cygwin' is xterm compatible
		{"cygwin", xterm_keys, xterm_funcs},
	}

	// try compatibility variants
	for _, it := range compat_table {
		if strings.Contains(name, it.partial) {
			keys = it.keys
			funcs = it.funcs
			return nil
		}
	}

	return errors.New("termbox: unsupported terminal")
}

func setup_term() (err error) {
	var data []byte
	var header [6]int16
	var str_offset, table_offset int16

	data, err = load_terminfo()
	if err != nil {
		return setup_term_builtin()
	}

	rd := bytes.NewReader(data)
	// 0: magic number, 1: size of names section, 2: size of boolean section, 3:
	// size of numbers section (in integers), 4: size of the strings section (in
	// integers), 5: size of the string table

	err = binary.Read(rd, binary.LittleEndian, header[:])
	if err != nil {
		return
	}

	if (header[1]+header[2])%2 != 0 {
		// old quirk to align everything on word boundaries
		header[2] += 1
	}
	str_offset = ti_header_length + header[1] + header[2] + 2*header[3]
	table_offset = str_offset + 2*header[4]

	keys = make([]string, 0xFFFF-key_min)
	for i, _ := range keys {
		keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset)
		if err != nil {
			return
		}
	}
	funcs = make([]string, t_max_funcs)
	// the last two entries are reserved for mouse. because the table offset is
	// not there, the two entries have to fill in manually
	for i, _ := range funcs[:len(funcs)-2] {
		funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset)
		if err != nil {
			return
		}
	}
	funcs[t_max_funcs-2] = "\x1b[?1000h"
	funcs[t_max_funcs-1] = "\x1b[?1000l"
	return nil
}

func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) {
	var off int16

	_, err := rd.Seek(int64(str_off), 0)
	if err != nil {
		return "", err
	}
	err = binary.Read(rd, binary.LittleEndian, &off)
	if err != nil {
		return "", err
	}
	_, err = rd.Seek(int64(table+off), 0)
	if err != nil {
		return "", err
	}
	var bs []byte
	for {
		b, err := rd.ReadByte()
		if err != nil {
			return "", err
		}
		if b == byte(0x00) {
			break
		}
		bs = append(bs, b)
	}
	return string(bs), nil
}

// "Maps" the function constants from termbox.go to the number of the respective
// string capability in the terminfo file. Taken from (ncurses) term.h.
var ti_funcs = []int16{
	28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
}

// Same as above for the special keys.
var ti_keys = []int16{
	66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70,
	71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83,
}
<|endoftext|>


<commit_before>// Copyright 2014 Mathias Monnerville. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.

// Package mango is a library for the MangoPay service v2.
//
// MangoPay is a platform that allows to accept payments and manage e-money
// using wallets. See http://www.mangopay.com.
//
// First, create an account with a unique Id to authenticate to the service:
//  conf, err := mango.RegisterClient("myclientid", "My Company",
//      "contact@company.com", mango.Sandbox)
//  if err != nil {
//      panic(err)
//  }
// Or use existing credentials:
//  conf, err := mango.NewConfig("myclientid", "My Company",
//      "contact@company.com", "passwd", "sandbox")
//
// Then, choose an authentication mode (OAuth2.0 or Basic) to use with the service:
//  service, err := mango.NewMangoPay(conf, mango.OAuth)
package mango

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strings"
	"time"
)

// Request execution environment (production or sandbox).
type ExecEnvironment int

const (
	Production ExecEnvironment = iota
	Sandbox
)

// Base URLs to execution environements
var rootURLs = map[ExecEnvironment]string{
	Production: "https://api.mangopay.com/v2/",
	Sandbox:    "https://api.sandbox.mangopay.com/v2/",
}

// The default HTTP client to use with the MangoPay api.
var DefaultClient = &http.Client{
	Transport: &http.Transport{
		// Use TLS 1.1 as maximum version acceptable. TLS 1.2 (which is the
		// default used if not specified) seems no more supported by MangoPay
		// servers (used to be working though). Using TLS 1.2 results in
		// "connection reset by peer" errors.
		TLSClientConfig: &tls.Config{MaxVersion: tls.VersionTLS11},
	},
}

// The Mangopay service.
type MangoPay struct {
	clientId   string // MangoPay partner ID
	password   string
	env        ExecEnvironment // Live or testing env
	rootURL    *url.URL        // Base API URL for the current execution environment
	verbosity  Level
	authMethod AuthMode
	// To track the current token during its lifetime
	oauth *oAuth2
}

// ProcessIdent identifies the current operation.
type ProcessIdent struct {
	Id           string
	Tag          string
	CreationDate int64
}

// ProcessReply holds commong fields part of MangoPay API replies.
type ProcessReply struct {
	ProcessIdent
	Status        string
	ResultCode    string
	ResultMessage string
	ExecutionDate int64
}

// NewMangoPay creates a suitable environment for accessing
// the web service. Default verbosity level is set to Info, which can be
// changed through the use of Option().
func NewMangoPay(auth *Config, mode AuthMode) (*MangoPay, error) {
	if auth == nil {
		return nil, errors.New("nil config")
	}
	if auth.Env == "sandbox" {
		auth.env = Sandbox
	} else if auth.Env == "production" {
		auth.env = Production
	} else {
		return nil, errors.New("unknown exec environment: " + auth.Env)
	}
	u, err := url.Parse(rootURLs[auth.env])
	if err != nil {
		return nil, err
	}
	return &MangoPay{auth.ClientId, auth.Passphrase, auth.env, u, Info, mode, nil}, nil
}

// Option set various options like verbosity etc.
func (m *MangoPay) Option(opts ...option) {
	for _, opt := range opts {
		opt(m)
	}
}

// request prepares and sends a well formatted HTTP request to the
// mangopay service.
func (s *MangoPay) request(ma mangoAction, data JsonObject) (*http.Response, error) {
	mr, ok := mangoRequests[ma]
	if !ok {
		return nil, errors.New("Action not implemented.")
	}

	// Create the submit url
	path := mr.Path
	if mr.PathValues != nil {
		// Substitute path variables, if any
		for name, _ := range mr.PathValues {
			if _, ok := data[name]; !ok {
				return nil, errors.New(fmt.Sprintf("missing keyword %s", name))
			}
			path = strings.Replace(path, "{{"+name+"}}", fmt.Sprintf("%v", data[name]), -1)
		}
	} else {
		path = mr.Path
	}

	body, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}
	resp, err := s.rawRequest(mr.Method, "application/json",
		fmt.Sprintf("%s%s%s", s.rootURL, s.clientId, path), body, true)
	return resp, err
}

// rawRequest sends an HTTP request with method method to an arbitrary URI.
func (s *MangoPay) rawRequest(method, contentType string, uri string, body []byte, useAuth bool) (*http.Response, error) {
	if contentType == "" {
		return nil, errors.New("empty request's content type")
	}
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(method, u.String(), strings.NewReader(string(body)))
	if err != nil {
		return nil, err
	}

	// Set header for basic auth
	if useAuth {
		if s.authMethod == BasicAuth {
			req.Header.Set("Authorization", basicAuthorization(s.clientId, s.password))
		} else {
			o, err := oAuthAuthorization(s)
			if err != nil {
				return nil, err
			}
			req.Header.Set("Authorization", o)
		}
	}
	req.Header.Set("Content-Type", contentType)

	if s.verbosity == Debug {
		fmt.Println(">>>>>>>>>>>>>>>>>>>>>> DEBUG REQUEST")
		fmt.Printf("%s %s\n\n", req.Method, req.URL.String())
		for k, v := range req.Header {
			for _, j := range v {
				fmt.Printf("%s: %v\n", k, j)
			}
		}
		rb := string(body)
		if rb != "null" {
			fmt.Printf("\n%s\n", rb)
		}
		fmt.Println("\nSending request ...")
		fmt.Println("<<<<<<<<<<<<<<<<<<<<<< DEBUG REQUEST")
	}

	// Send request
	resp, err := DefaultClient.Do(req)

	// Handle reponse status code
	if err == nil && resp.StatusCode != http.StatusOK {
		j := JsonObject{}
		err = s.unMarshalJSONResponse(resp, &j)
		if err != nil {
			return nil, err
		}
		errmsg := ""
		if msg, ok := j["Message"]; ok {
			errmsg = fmt.Sprintf("Status %d: %s", resp.StatusCode, msg.(string))
		} else {
			errmsg = fmt.Sprintf("Status %d; body: '%s'", resp.StatusCode, j)
		}
		if private, ok := j["errors"]; ok {
			errmsg += fmt.Sprintf("(details :%v)", private)
		}
		err = errors.New(errmsg)
	}
	return resp, err
}

// Unmarshal a JSON HTTP response into an instance.
func (m *MangoPay) unMarshalJSONResponse(resp *http.Response, v interface{}) error {
	if resp == nil {
		return errors.New("can't unmarshal nil response")
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if m.verbosity == Debug {
		fmt.Println(">>>>>>>>>>>>>>>>>>>>>> DEBUG RESPONSE")
		fmt.Printf("Status code: %d\n\n", resp.StatusCode)
		for k, v := range resp.Header {
			for _, j := range v {
				fmt.Printf("%s: %v\n", k, j)
			}
		}
		fmt.Printf("\n%s\n", string(b))
		fmt.Println("<<<<<<<<<<<<<<<<<<<<<< DEBUG RESPONSE")
	}
	if err := json.Unmarshal(b, v); err != nil {
		return err
	}
	return nil
}

// Generic request for any object.
func (m *MangoPay) anyRequest(o interface{}, action mangoAction, data JsonObject) (interface{}, error) {
	resp, err := m.request(action, data)
	if err != nil {
		return nil, err
	}

	t := reflect.TypeOf(o)
	if t.Kind() == reflect.Ptr {
		v := reflect.ValueOf(o)
		t = reflect.Indirect(v).Type()
	}
	ins := reflect.New(t).Interface()
	if err := m.unMarshalJSONResponse(resp, ins); err != nil {
		return nil, err
	}
	return ins, nil
}

func unixTimeToString(t int64) string {
	if t > 0 {
		return time.Unix(t, 0).String()
	}
	return "Never"
}

// Use reflection to print data structures.
func struct2string(c interface{}) string {
	var b bytes.Buffer
	e := reflect.ValueOf(c).Elem()
	for i := 0; i < e.NumField(); i++ {
		sfield := e.Type().Field(i)
		// Skip unexported fields
		if sfield.PkgPath != "" {
			continue
		}
		name := sfield.Name
		val := e.Field(i).Interface()
		// Handle embedded types
		if sfield.Anonymous {
			b.Write([]byte(struct2string(e.Field(i).Addr().Interface())))
		} else {
			if name == "CreationDate" || name == "ExecutionDate" ||
				name == "Birthday" {
				val = unixTimeToString(val.(int64))
			}
			b.Write([]byte(fmt.Sprintf("%-24s: %v\n", name, val)))
		}
	}
	return b.String()
}

func consumerId(c Consumer) string {
	id := ""
	switch c.(type) {
	case *LegalUser:
		id = c.(*LegalUser).Id
	case *NaturalUser:
		id = c.(*NaturalUser).Id
	}
	return id
}
<commit_msg>Fix handling of 204 HTTP status code<commit_after>// Copyright 2014 Mathias Monnerville. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.

// Package mango is a library for the MangoPay service v2.
//
// MangoPay is a platform that allows to accept payments and manage e-money
// using wallets. See http://www.mangopay.com.
//
// First, create an account with a unique Id to authenticate to the service:
//  conf, err := mango.RegisterClient("myclientid", "My Company",
//      "contact@company.com", mango.Sandbox)
//  if err != nil {
//      panic(err)
//  }
// Or use existing credentials:
//  conf, err := mango.NewConfig("myclientid", "My Company",
//      "contact@company.com", "passwd", "sandbox")
//
// Then, choose an authentication mode (OAuth2.0 or Basic) to use with the service:
//  service, err := mango.NewMangoPay(conf, mango.OAuth)
package mango

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strings"
	"time"
)

// Request execution environment (production or sandbox).
type ExecEnvironment int

const (
	Production ExecEnvironment = iota
	Sandbox
)

// Base URLs to execution environements
var rootURLs = map[ExecEnvironment]string{
	Production: "https://api.mangopay.com/v2/",
	Sandbox:    "https://api.sandbox.mangopay.com/v2/",
}

// The default HTTP client to use with the MangoPay api.
var DefaultClient = &http.Client{
	Transport: &http.Transport{
		// Use TLS 1.1 as maximum version acceptable. TLS 1.2 (which is the
		// default used if not specified) seems no more supported by MangoPay
		// servers (used to be working though). Using TLS 1.2 results in
		// "connection reset by peer" errors.
		TLSClientConfig: &tls.Config{MaxVersion: tls.VersionTLS11},
	},
}

// The Mangopay service.
type MangoPay struct {
	clientId   string // MangoPay partner ID
	password   string
	env        ExecEnvironment // Live or testing env
	rootURL    *url.URL        // Base API URL for the current execution environment
	verbosity  Level
	authMethod AuthMode
	// To track the current token during its lifetime
	oauth *oAuth2
}

// ProcessIdent identifies the current operation.
type ProcessIdent struct {
	Id           string
	Tag          string
	CreationDate int64
}

// ProcessReply holds commong fields part of MangoPay API replies.
type ProcessReply struct {
	ProcessIdent
	Status        string
	ResultCode    string
	ResultMessage string
	ExecutionDate int64
}

// NewMangoPay creates a suitable environment for accessing
// the web service. Default verbosity level is set to Info, which can be
// changed through the use of Option().
func NewMangoPay(auth *Config, mode AuthMode) (*MangoPay, error) {
	if auth == nil {
		return nil, errors.New("nil config")
	}
	if auth.Env == "sandbox" {
		auth.env = Sandbox
	} else if auth.Env == "production" {
		auth.env = Production
	} else {
		return nil, errors.New("unknown exec environment: " + auth.Env)
	}
	u, err := url.Parse(rootURLs[auth.env])
	if err != nil {
		return nil, err
	}
	return &MangoPay{auth.ClientId, auth.Passphrase, auth.env, u, Info, mode, nil}, nil
}

// Option set various options like verbosity etc.
func (m *MangoPay) Option(opts ...option) {
	for _, opt := range opts {
		opt(m)
	}
}

// request prepares and sends a well formatted HTTP request to the
// mangopay service.
func (s *MangoPay) request(ma mangoAction, data JsonObject) (*http.Response, error) {
	mr, ok := mangoRequests[ma]
	if !ok {
		return nil, errors.New("Action not implemented.")
	}

	// Create the submit url
	path := mr.Path
	if mr.PathValues != nil {
		// Substitute path variables, if any
		for name, _ := range mr.PathValues {
			if _, ok := data[name]; !ok {
				return nil, errors.New(fmt.Sprintf("missing keyword %s", name))
			}
			path = strings.Replace(path, "{{"+name+"}}", fmt.Sprintf("%v", data[name]), -1)
		}
	} else {
		path = mr.Path
	}

	body, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}
	resp, err := s.rawRequest(mr.Method, "application/json",
		fmt.Sprintf("%s%s%s", s.rootURL, s.clientId, path), body, true)
	return resp, err
}

// rawRequest sends an HTTP request with method method to an arbitrary URI.
func (s *MangoPay) rawRequest(method, contentType string, uri string, body []byte, useAuth bool) (*http.Response, error) {
	if contentType == "" {
		return nil, errors.New("empty request's content type")
	}
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(method, u.String(), strings.NewReader(string(body)))
	if err != nil {
		return nil, err
	}

	// Set header for basic auth
	if useAuth {
		if s.authMethod == BasicAuth {
			req.Header.Set("Authorization", basicAuthorization(s.clientId, s.password))
		} else {
			o, err := oAuthAuthorization(s)
			if err != nil {
				return nil, err
			}
			req.Header.Set("Authorization", o)
		}
	}
	req.Header.Set("Content-Type", contentType)

	if s.verbosity == Debug {
		fmt.Println(">>>>>>>>>>>>>>>>>>>>>> DEBUG REQUEST")
		fmt.Printf("%s %s\n\n", req.Method, req.URL.String())
		for k, v := range req.Header {
			for _, j := range v {
				fmt.Printf("%s: %v\n", k, j)
			}
		}
		rb := string(body)
		if rb != "null" {
			fmt.Printf("\n%s\n", rb)
		}
		fmt.Println("\nSending request ...")
		fmt.Println("<<<<<<<<<<<<<<<<<<<<<< DEBUG REQUEST")
	}

	// Send request
	resp, err := DefaultClient.Do(req)

	// Handle reponse status code
	if err == nil && resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
		j := JsonObject{}
		err = s.unMarshalJSONResponse(resp, &j)
		if err != nil {
			return nil, err
		}
		errmsg := ""
		if msg, ok := j["Message"]; ok {
			errmsg = fmt.Sprintf("Status %d: %s", resp.StatusCode, msg.(string))
		} else {
			errmsg = fmt.Sprintf("Status %d; body: '%s'", resp.StatusCode, j)
		}
		if private, ok := j["errors"]; ok {
			errmsg += fmt.Sprintf("(details :%v)", private)
		}
		err = errors.New(errmsg)
	}
	return resp, err
}

// Unmarshal a JSON HTTP response into an instance.
func (m *MangoPay) unMarshalJSONResponse(resp *http.Response, v interface{}) error {
	if resp == nil {
		return errors.New("can't unmarshal nil response")
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if m.verbosity == Debug {
		fmt.Println(">>>>>>>>>>>>>>>>>>>>>> DEBUG RESPONSE")
		fmt.Printf("Status code: %d\n\n", resp.StatusCode)
		for k, v := range resp.Header {
			for _, j := range v {
				fmt.Printf("%s: %v\n", k, j)
			}
		}
		fmt.Printf("\n%s\n", string(b))
		fmt.Println("<<<<<<<<<<<<<<<<<<<<<< DEBUG RESPONSE")
	}
	if err := json.Unmarshal(b, v); err != nil {
		return err
	}
	return nil
}

// Generic request for any object.
func (m *MangoPay) anyRequest(o interface{}, action mangoAction, data JsonObject) (interface{}, error) {
	resp, err := m.request(action, data)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == http.StatusNoContent {
		return nil, nil
	}

	t := reflect.TypeOf(o)
	if t.Kind() == reflect.Ptr {
		v := reflect.ValueOf(o)
		t = reflect.Indirect(v).Type()
	}
	ins := reflect.New(t).Interface()
	if err := m.unMarshalJSONResponse(resp, ins); err != nil {
		return nil, err
	}
	return ins, nil
}

func unixTimeToString(t int64) string {
	if t > 0 {
		return time.Unix(t, 0).String()
	}
	return "Never"
}

// Use reflection to print data structures.
func struct2string(c interface{}) string {
	var b bytes.Buffer
	e := reflect.ValueOf(c).Elem()
	for i := 0; i < e.NumField(); i++ {
		sfield := e.Type().Field(i)
		// Skip unexported fields
		if sfield.PkgPath != "" {
			continue
		}
		name := sfield.Name
		val := e.Field(i).Interface()
		// Handle embedded types
		if sfield.Anonymous {
			b.Write([]byte(struct2string(e.Field(i).Addr().Interface())))
		} else {
			if name == "CreationDate" || name == "ExecutionDate" ||
				name == "Birthday" {
				val = unixTimeToString(val.(int64))
			}
			b.Write([]byte(fmt.Sprintf("%-24s: %v\n", name, val)))
		}
	}
	return b.String()
}

func consumerId(c Consumer) string {
	id := ""
	switch c.(type) {
	case *LegalUser:
		id = c.(*LegalUser).Id
	case *NaturalUser:
		id = c.(*NaturalUser).Id
	}
	return id
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package web

import (
	"net/http"
	"sync"

	"github.com/issue9/logs"
	"github.com/issue9/session"
	"github.com/issue9/session/providers"
	"github.com/issue9/session/stores"
)

var (
	sessionMgr *session.Manager
	sessions   = map[*http.Request]*session.Session{}
	sessionsMu sync.Mutex
)

// 初始化session
// vars接受以下四个参数：idname,lifetime,type,saveDir
func initSession() error {
	c := cfg.Session
	prv := providers.NewCookie(c.Lifetime, c.IDName, "/", "", true)

	switch c.Type {
	case "", "memory":
		sessionMgr = session.New(stores.NewMemory(c.Lifetime), prv)
	case "file":
		f, err := stores.NewFile(c.SaveDir, c.Lifetime, logs.ERROR())
		if err != nil {
			panic(err)
		}
		sessionMgr = session.New(f, prv)
	default:
		panic("initSession:无效的session.type值")
	}

	return nil
}

// 获取Session实例。
func Session(w http.ResponseWriter, req *http.Request) *session.Session {
	sessionsMu.Lock()
	defer sessionsMu.Unlock()

	if sess, found := sessions[req]; found {
		return sess
	}

	sess, err := sessionMgr.Start(w, req)
	if err != nil {
		logs.Error(err)
		return nil
	}

	sessions[req] = sess
	return sess
}
<commit_msg>去掉initSession无用的返回值<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package web

import (
	"net/http"
	"sync"

	"github.com/issue9/logs"
	"github.com/issue9/session"
	"github.com/issue9/session/providers"
	"github.com/issue9/session/stores"
)

var (
	sessionMgr *session.Manager
	sessions   = map[*http.Request]*session.Session{}
	sessionsMu sync.Mutex
)

// 初始化session
// vars接受以下四个参数：idname,lifetime,type,saveDir
func initSession() {
	c := cfg.Session
	prv := providers.NewCookie(c.Lifetime, c.IDName, "/", "", true)

	switch c.Type {
	case "", "memory":
		sessionMgr = session.New(stores.NewMemory(c.Lifetime), prv)
	case "file":
		f, err := stores.NewFile(c.SaveDir, c.Lifetime, logs.ERROR())
		if err != nil {
			panic(err)
		}
		sessionMgr = session.New(f, prv)
	default:
		panic("initSession:无效的session.type值")
	}
}

// 获取Session实例。
func Session(w http.ResponseWriter, req *http.Request) *session.Session {
	sessionsMu.Lock()
	defer sessionsMu.Unlock()

	if sess, found := sessions[req]; found {
		return sess
	}

	sess, err := sessionMgr.Start(w, req)
	if err != nil {
		logs.Error(err)
		return nil
	}

	sessions[req] = sess
	return sess
}
<|endoftext|>


<commit_before>package smux

import (
	"io"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
)

const (
	defaultAcceptBacklog = 1024
	defaultCloseWait     = 1024
)

const (
	errBrokenPipe      = "broken pipe"
	errInvalidProtocol = "invalid protocol version"
)

// Session defines a multiplexed connection for streams
type Session struct {
	conn io.ReadWriteCloser

	config       *Config
	nextStreamID uint32 // next stream identifier

	tbf         chan struct{}      // token bucket
	frameQueues map[uint32][]Frame // stream input frame queue
	streams     map[uint32]*Stream // all streams in this session
	streamLock  sync.Mutex         // locks streams && frameQueues

	die            chan struct{} // flag session has died
	dieLock        sync.Mutex
	chAccepts      chan *Stream
	chClosedStream chan uint32

	dataReady int32 // flag data has arrived
}

func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
	s := new(Session)
	s.die = make(chan struct{})
	s.conn = conn
	s.config = config
	s.streams = make(map[uint32]*Stream)
	s.frameQueues = make(map[uint32][]Frame)
	s.chAccepts = make(chan *Stream, defaultAcceptBacklog)
	s.chClosedStream = make(chan uint32, defaultCloseWait)
	s.tbf = make(chan struct{}, config.MaxFrameTokens)
	for i := 0; i < config.MaxFrameTokens; i++ {
		s.tbf <- struct{}{}
	}
	if client {
		s.nextStreamID = 1
	} else {
		s.nextStreamID = 2
	}
	go s.recvLoop()
	go s.monitor()
	go s.keepalive()
	return s
}

// OpenStream is used to create a new stream
func (s *Session) OpenStream() (*Stream, error) {
	if s.IsClosed() {
		return nil, errors.New(errBrokenPipe)
	}

	sid := atomic.AddUint32(&s.nextStreamID, 2)
	stream := newStream(sid, s.config.MaxFrameSize, s)

	s.streamLock.Lock()
	s.streams[sid] = stream
	s.streamLock.Unlock()

	s.sendFrame(newFrame(cmdSYN, sid))
	return stream, nil
}

// AcceptStream is used to block until the next available stream
// is ready to be accepted.
func (s *Session) AcceptStream() (*Stream, error) {
	select {
	case stream := <-s.chAccepts:
		return stream, nil
	case <-s.die:
		return nil, errors.New(errBrokenPipe)
	}
}

// Close is used to close the session and all streams.
func (s *Session) Close() error {
	s.dieLock.Lock()
	defer s.dieLock.Unlock()

	select {
	case <-s.die:
		return errors.New(errBrokenPipe)
	default:
		s.streamLock.Lock()
		for k := range s.streams {
			s.streams[k].Close()
		}
		s.streamLock.Unlock()
		s.sendFrame(newFrame(cmdTerminate, 0))
		s.conn.Close()
		close(s.die)
	}
	return nil
}

// IsClosed does a safe check to see if we have shutdown
func (s *Session) IsClosed() bool {
	select {
	case <-s.die:
		return true
	default:
		return false
	}
}

// NumStreams returns the number of currently open streams
func (s *Session) NumStreams() int {
	s.streamLock.Lock()
	defer s.streamLock.Unlock()
	return len(s.streams)
}

// notify the session that a stream has closed
func (s *Session) streamClosed(sid uint32) {
	go func() {
		select {
		case s.chClosedStream <- sid:
		case <-s.die:
		}
	}()
}

// nonblocking read from session pool, for streams
func (s *Session) nioread(sid uint32) *Frame {
	s.streamLock.Lock()
	frames := s.frameQueues[sid]
	if len(frames) > 0 {
		f := frames[0]
		s.frameQueues[sid] = frames[1:]
		s.tbf <- struct{}{}
		s.streamLock.Unlock()
		return &f
	}
	s.streamLock.Unlock()
	return nil
}

// session read a frame from underlying connection
func (s *Session) readFrame(buffer []byte) (f Frame, err error) {
	if _, err := io.ReadFull(s.conn, buffer[:headerSize]); err != nil {
		return f, errors.Wrap(err, "readFrame")
	}

	dec := rawHeader(buffer)
	if dec.Version() != version {
		return f, errors.New(errInvalidProtocol)
	}

	if length := dec.Length(); length > 0 {
		if _, err := io.ReadFull(s.conn, buffer[headerSize:headerSize+length]); err != nil {
			return f, errors.Wrap(err, "readFrame")
		}
		f.UnmarshalBinary(buffer[:headerSize+length])
		return f, nil
	}
	f.UnmarshalBinary(buffer[:headerSize])
	return f, nil
}

// monitors streams
func (s *Session) monitor() {
	for {
		select {
		case sid := <-s.chClosedStream:
			s.streamLock.Lock()
			delete(s.streams, sid)
			ntokens := len(s.frameQueues[sid])
			delete(s.frameQueues, sid)
			s.streamLock.Unlock()
			for i := 0; i < ntokens; i++ { // return remaining tokens to the bucket
				s.tbf <- struct{}{}
			}
		case <-s.die:
			return
		}
	}
}

// recvLoop keeps on reading from underlying connection if tokens are available
func (s *Session) recvLoop() {
	buffer := make([]byte, (1<<16)+headerSize)
	for {
		select {
		case <-s.tbf:
			if f, err := s.readFrame(buffer); err == nil {
				switch f.cmd {
				case cmdNOP:
					s.tbf <- struct{}{}
				case cmdTerminate:
					s.Close()
					return
				case cmdSYN:
					s.streamLock.Lock()
					if _, ok := s.streams[f.sid]; !ok {
						s.streams[f.sid] = newStream(f.sid, s.config.MaxFrameSize, s)
						s.chAccepts <- s.streams[f.sid]
					} else { // stream exists, RST the peer
						s.sendFrame(newFrame(cmdRST, f.sid))
					}
					s.streamLock.Unlock()
					s.tbf <- struct{}{}
				case cmdRST:
					s.streamLock.Lock()
					if _, ok := s.streams[f.sid]; ok {
						s.streams[f.sid].Close()
					} else { // must do nothing if stream is absent
					}
					s.streamLock.Unlock()
					s.tbf <- struct{}{}
				case cmdPSH:
					s.streamLock.Lock()
					if stream, ok := s.streams[f.sid]; ok {
						s.frameQueues[f.sid] = append(s.frameQueues[f.sid], f)
						stream.notifyReadEvent()
					} else { // stream is absent
						s.sendFrame(newFrame(cmdRST, f.sid))
						s.tbf <- struct{}{}
					}
					s.streamLock.Unlock()
				default:
					s.Close()
					return
				}
				atomic.StoreInt32(&s.dataReady, 1)
			} else {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) keepalive() {
	tickerPing := time.NewTicker(s.config.KeepAliveInterval)
	tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
	defer tickerPing.Stop()
	defer tickerTimeout.Stop()
	for {
		select {
		case <-tickerPing.C:
			s.sendFrame(newFrame(cmdNOP, 0))
		case <-tickerTimeout.C:
			if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) sendFrame(f Frame) {
	bts, _ := f.MarshalBinary()
	s.conn.Write(bts)
}
<commit_msg>simplify token return<commit_after>package smux

import (
	"io"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
)

const (
	defaultAcceptBacklog = 1024
	defaultCloseWait     = 1024
)

const (
	errBrokenPipe      = "broken pipe"
	errInvalidProtocol = "invalid protocol version"
)

// Session defines a multiplexed connection for streams
type Session struct {
	conn io.ReadWriteCloser

	config       *Config
	nextStreamID uint32 // next stream identifier

	tbf         chan struct{}      // token bucket
	frameQueues map[uint32][]Frame // stream input frame queue
	streams     map[uint32]*Stream // all streams in this session
	streamLock  sync.Mutex         // locks streams && frameQueues

	die            chan struct{} // flag session has died
	dieLock        sync.Mutex
	chAccepts      chan *Stream
	chClosedStream chan uint32

	dataReady int32 // flag data has arrived
}

func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
	s := new(Session)
	s.die = make(chan struct{})
	s.conn = conn
	s.config = config
	s.streams = make(map[uint32]*Stream)
	s.frameQueues = make(map[uint32][]Frame)
	s.chAccepts = make(chan *Stream, defaultAcceptBacklog)
	s.chClosedStream = make(chan uint32, defaultCloseWait)
	s.tbf = make(chan struct{}, config.MaxFrameTokens)
	for i := 0; i < config.MaxFrameTokens; i++ {
		s.tbf <- struct{}{}
	}
	if client {
		s.nextStreamID = 1
	} else {
		s.nextStreamID = 2
	}
	go s.recvLoop()
	go s.monitor()
	go s.keepalive()
	return s
}

// OpenStream is used to create a new stream
func (s *Session) OpenStream() (*Stream, error) {
	if s.IsClosed() {
		return nil, errors.New(errBrokenPipe)
	}

	sid := atomic.AddUint32(&s.nextStreamID, 2)
	stream := newStream(sid, s.config.MaxFrameSize, s)

	s.streamLock.Lock()
	s.streams[sid] = stream
	s.streamLock.Unlock()

	s.sendFrame(newFrame(cmdSYN, sid))
	return stream, nil
}

// AcceptStream is used to block until the next available stream
// is ready to be accepted.
func (s *Session) AcceptStream() (*Stream, error) {
	select {
	case stream := <-s.chAccepts:
		return stream, nil
	case <-s.die:
		return nil, errors.New(errBrokenPipe)
	}
}

// Close is used to close the session and all streams.
func (s *Session) Close() error {
	s.dieLock.Lock()
	defer s.dieLock.Unlock()

	select {
	case <-s.die:
		return errors.New(errBrokenPipe)
	default:
		s.streamLock.Lock()
		for k := range s.streams {
			s.streams[k].Close()
		}
		s.streamLock.Unlock()
		s.sendFrame(newFrame(cmdTerminate, 0))
		s.conn.Close()
		close(s.die)
	}
	return nil
}

// IsClosed does a safe check to see if we have shutdown
func (s *Session) IsClosed() bool {
	select {
	case <-s.die:
		return true
	default:
		return false
	}
}

// NumStreams returns the number of currently open streams
func (s *Session) NumStreams() int {
	s.streamLock.Lock()
	defer s.streamLock.Unlock()
	return len(s.streams)
}

// notify the session that a stream has closed
func (s *Session) streamClosed(sid uint32) {
	go func() {
		select {
		case s.chClosedStream <- sid:
		case <-s.die:
		}
	}()
}

// nonblocking read from session pool, for streams
func (s *Session) nioread(sid uint32) *Frame {
	s.streamLock.Lock()
	frames := s.frameQueues[sid]
	if len(frames) > 0 {
		f := frames[0]
		s.frameQueues[sid] = frames[1:]
		s.tbf <- struct{}{}
		s.streamLock.Unlock()
		return &f
	}
	s.streamLock.Unlock()
	return nil
}

// session read a frame from underlying connection
func (s *Session) readFrame(buffer []byte) (f Frame, err error) {
	if _, err := io.ReadFull(s.conn, buffer[:headerSize]); err != nil {
		return f, errors.Wrap(err, "readFrame")
	}

	dec := rawHeader(buffer)
	if dec.Version() != version {
		return f, errors.New(errInvalidProtocol)
	}

	if length := dec.Length(); length > 0 {
		if _, err := io.ReadFull(s.conn, buffer[headerSize:headerSize+length]); err != nil {
			return f, errors.Wrap(err, "readFrame")
		}
		f.UnmarshalBinary(buffer[:headerSize+length])
		return f, nil
	}
	f.UnmarshalBinary(buffer[:headerSize])
	return f, nil
}

// monitors streams
func (s *Session) monitor() {
	for {
		select {
		case sid := <-s.chClosedStream:
			s.streamLock.Lock()
			delete(s.streams, sid)
			ntokens := len(s.frameQueues[sid])
			delete(s.frameQueues, sid)
			s.streamLock.Unlock()
			for i := 0; i < ntokens; i++ { // return remaining tokens to the bucket
				s.tbf <- struct{}{}
			}
		case <-s.die:
			return
		}
	}
}

// recvLoop keeps on reading from underlying connection if tokens are available
func (s *Session) recvLoop() {
	buffer := make([]byte, (1<<16)+headerSize)
	for {
		select {
		case <-s.tbf:
			s.tbf <- struct{}{}
			if f, err := s.readFrame(buffer); err == nil {
				switch f.cmd {
				case cmdNOP:
				case cmdTerminate:
					s.Close()
					return
				case cmdSYN:
					s.streamLock.Lock()
					if _, ok := s.streams[f.sid]; !ok {
						s.streams[f.sid] = newStream(f.sid, s.config.MaxFrameSize, s)
						s.chAccepts <- s.streams[f.sid]
					} else { // stream exists, RST the peer
						s.sendFrame(newFrame(cmdRST, f.sid))
					}
					s.streamLock.Unlock()
				case cmdRST:
					s.streamLock.Lock()
					if _, ok := s.streams[f.sid]; ok {
						s.streams[f.sid].Close()
					} else { // must do nothing if stream is absent
					}
					s.streamLock.Unlock()
				case cmdPSH:
					s.streamLock.Lock()
					if stream, ok := s.streams[f.sid]; ok {
						s.frameQueues[f.sid] = append(s.frameQueues[f.sid], f)
						stream.notifyReadEvent()
						<-s.tbf // remove a token
					} else { // stream is absent
						s.sendFrame(newFrame(cmdRST, f.sid))
					}
					s.streamLock.Unlock()
				default:
					s.Close()
					return
				}
				atomic.StoreInt32(&s.dataReady, 1)
			} else {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) keepalive() {
	tickerPing := time.NewTicker(s.config.KeepAliveInterval)
	tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
	defer tickerPing.Stop()
	defer tickerTimeout.Stop()
	for {
		select {
		case <-tickerPing.C:
			s.sendFrame(newFrame(cmdNOP, 0))
		case <-tickerTimeout.C:
			if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) sendFrame(f Frame) {
	bts, _ := f.MarshalBinary()
	s.conn.Write(bts)
}
<|endoftext|>