<commit_before>package tgbot

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

// TelegramBot allows to interact with Telegram Bot API
type TelegramBot struct {
	apiKey       string
	lastUpdateID int
}

// New returns a new TelegramBot instance
func New(apiKey string) (TelegramBot, error) {
	var err error
	var bot = TelegramBot{
		apiKey:       apiKey,
		lastUpdateID: -1,
	}

	_, err = bot.GetMe()
	if err != nil {
		return TelegramBot{}, err
	}

	_, err = bot.DeleteWebhook()
	if err != nil {
		return TelegramBot{}, err
	}

	return bot, nil
}

// PollConfig represents bot's polling configuration
type PollConfig struct {
	Callback func([]Update)
	Delay    int
}

// Poll starts updates polling
func (b *TelegramBot) Poll(config PollConfig) error {
	for {
		var updates, err = b.GetUpdates(GetUpdatesConfig{Offset: b.lastUpdateID + 1})
		if err == nil && len(updates) != 0 {
			b.lastUpdateID = updates[len(updates)-1].UpdateID
			go config.Callback(updates)
		}

		time.Sleep(time.Duration(config.Delay) * time.Millisecond)
	}
}

// ListenConfig represents bot's webhook configuration
type ListenConfig struct {
	Callback       func([]Update)
	Host           string
	Port           uint16
	KeyFilename    string
	CertFilename   string
	MaxConnections int
	AllowedUpdates []string
}

// Listen starts HTTPS server to receive updates
func (b *TelegramBot) Listen(config ListenConfig) error {
	http.HandleFunc("/"+b.apiKey, func(w http.ResponseWriter, req *http.Request) {
		var update Update
		err := json.NewDecoder(req.Body).Decode(&update)
		if err != nil {
			return
		}

		defer req.Body.Close()
		config.Callback([]Update{update})
	})

	_, err := b.SetWebhook(SetWebhookConfig{
		URL:            fmt.Sprintf("https://%s:%d/%s", config.Host, config.Port, b.apiKey),
		Certificate:    FilePath(config.CertFilename),
		AllowedUpdates: config.AllowedUpdates,
	})

	if err != nil {
		return err
	}

	return http.ListenAndServeTLS(fmt.Sprintf(":%d", config.Port), config.CertFilename, config.KeyFilename, nil)
}

// GetMe returns basic information about the bot
func (b TelegramBot) GetMe() (user User, err error) {
	return user, b.sendResuest("getMe", nil, &user)
}

// GetUpdates allows to get new updates
func (b TelegramBot) GetUpdates(config GetUpdatesConfig) (updates []Update, err error) {
	return updates, b.sendResuest("getUpdates", config, &updates)
}

// SetWebhook used to specify url and receive incoming updates via an outgoing webhook
func (b TelegramBot) SetWebhook(config SetWebhookConfig) (success bool, err error) {
	return success, b.sendResuest("setWebhook", config, &success)
}

// DeleteWebhook used to remove webhook integration
func (b TelegramBot) DeleteWebhook() (success bool, err error) {
	return success, b.sendResuest("deleteWebhook", nil, &success)
}

// GetWebhookInfo user to get current webhook status
func (b TelegramBot) GetWebhookInfo() (info WebhookInfo, err error) {
	return info, b.sendResuest("getWebhookInfo", nil, &info)
}

// SendMessage sends text message
func (b TelegramBot) SendMessage(config SendMessageConfig) (message Message, err error) {
	return message, b.sendResuest("sendMessage", config, &message)
}

// ForwardMessage re-sends message of any type
func (b TelegramBot) ForwardMessage(config ForwardMessageConfig) (message Message, err error) {
	return message, b.sendResuest("forwardMessage", config, &message)
}

// SendPhoto sends photo message
func (b TelegramBot) SendPhoto(config SendPhotoConfig) (message Message, err error) {
	return message, b.sendResuest("sendPhoto", config, &message)
}

// SendAudio sends audio message
func (b TelegramBot) SendAudio(config SendAudioConfig) (message Message, err error) {
	return message, b.sendResuest("sendAudio", config, &message)
}

// SendDocument sends document message
func (b TelegramBot) SendDocument(config SendDocumentConfig) (message Message, err error) {
	return message, b.sendResuest("sendDocument", config, &message)
}

// SendVideo sends video message
func (b TelegramBot) SendVideo(config SendVideoConfig) (message Message, err error) {
	return message, b.sendResuest("sendVideo", config, &message)
}

// SendVoice sends voice note message
func (b TelegramBot) SendVoice(config SendVoiceConfig) (message Message, err error) {
	return message, b.sendResuest("sendVoice", config, &message)
}

// SendVideoNote sends video note message
func (b TelegramBot) SendVideoNote(config SendVideoNoteConfig) (message Message, err error) {
	return message, b.sendResuest("sendVideoNote", config, &message)
}

// SendLocation sends location message
func (b TelegramBot) SendLocation(config SendLocationConfig) (message Message, err error) {
	return message, b.sendResuest("sendLocation", config, &message)
}

// SendVenue sends information about a venue
func (b TelegramBot) SendVenue(config SendVenueConfig) (message Message, err error) {
	return message, b.sendResuest("sendVenue", config, &message)
}

// SendContact sends phone contact
func (b TelegramBot) SendContact(config SendContactConfig) (message Message, err error) {
	return message, b.sendResuest("sendContact", config, &message)
}

// SendSticker sends sticker
func (b TelegramBot) SendSticker(config SendStickerConfig) (message Message, err error) {
	return message, b.sendResuest("sendSticker", config, &message)
}

// SendChatAction sends phone contact
func (b TelegramBot) SendChatAction(config SendChatActionConfig) (success bool, err error) {
	return success, b.sendResuest("sendChatAction", config, &success)
}

// GetUserProfilePhotos returns user profile photos
func (b TelegramBot) GetUserProfilePhotos(config GetUserProfilePhotosConfig) (photos UserProfilePhotos, err error) {
	return photos, b.sendResuest("getUserProfilePhotos", config, &photos)
}

// GetFile allows to get basic info about a file and prepare it for downloading
func (b TelegramBot) GetFile(config GetFileConfig) (file File, err error) {
	return file, b.sendResuest("getFile", config, &file)
}

// KickChatMember allows to kick user from a group, a supergroup or a channel
func (b TelegramBot) KickChatMember(config KickChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("kickChatMember", config, &success)
}

// UnbanChatMember allows to unban user from a group, a supergroup or a channel
func (b TelegramBot) UnbanChatMember(config UnbanChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("unbanChatMember", config, &success)
}

// RestrictChatMember allows to restrict a user in a supergroup
func (b TelegramBot) RestrictChatMember(config RestrictChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("restrictChatMember", config, &success)
}

// PromoteChatMember allows to promote or demote a user in a supergroup or a channel
func (b TelegramBot) PromoteChatMember(config PromoteChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("promoteChatMember", config, &success)
}

// ExportChatInviteLink allows to export an invite link to a supergroup or a channel
func (b TelegramBot) ExportChatInviteLink(config ExportChatInviteLinkConfig) (link string, err error) {
	return link, b.sendResuest("exportChatInviteLink", config, &link)
}

// SetChatPhoto allows to set a new profile photo for the chat
func (b TelegramBot) SetChatPhoto(config SetChatPhotoConfig) (success bool, err error) {
	return success, b.sendResuest("setChatPhoto", config, &success)
}

// DeleteChatPhoto allows to delete a new profile photo for the chat
func (b TelegramBot) DeleteChatPhoto(config DeleteChatPhotoConfig) (success bool, err error) {
	return success, b.sendResuest("deleteChatPhoto", config, &success)
}

// SetChatTitle allows to change the title of a chat
func (b TelegramBot) SetChatTitle(config SetChatTitleConfig) (success bool, err error) {
	return success, b.sendResuest("setChatTitle", config, &success)
}

// SetChatDescription allows to change the description of a chat
func (b TelegramBot) SetChatDescription(config SetChatDescriptionConfig) (success bool, err error) {
	return success, b.sendResuest("setChatDescription", config, &success)
}

// PinChatMessage allows to pin a message in a supergroup
func (b TelegramBot) PinChatMessage(config PinChatMessageConfig) (success bool, err error) {
	return success, b.sendResuest("pinChatMessage", config, &success)
}

// UnpinChatMessage allows to unpin a message in a supergroup
func (b TelegramBot) UnpinChatMessage(config UnpinChatMessageConfig) (success bool, err error) {
	return success, b.sendResuest("unpinChatMessage", config, &success)
}

// LeaveChat allows to leave a group, supergroup or channel
func (b TelegramBot) LeaveChat(config LeaveChatConfig) (success bool, err error) {
	return success, b.sendResuest("leaveChat", config, &success)
}

// GetChat allows to get up to date information about the chat
func (b TelegramBot) GetChat(config GetChatConfig) (chat Chat, err error) {
	return chat, b.sendResuest("getChat", config, &chat)
}

// GetChatAdministrators allows to get a list of administrators in a chat
func (b TelegramBot) GetChatAdministrators(config GetChatAdministratorsConfig) (members []ChatMember, err error) {
	return members, b.sendResuest("getChatAdministrators", config, &members)
}

// GetChatMembersCount allows get the number of members in a chat
func (b TelegramBot) GetChatMembersCount(config GetChatMembersCountConfig) (count int, err error) {
	return count, b.sendResuest("getChatMembersCount", config, &count)
}

// GetChatMember allows to get information about a member of a chat
func (b TelegramBot) GetChatMember(config GetChatMemberConfig) (member ChatMember, err error) {
	return member, b.sendResuest("getChatMember", config, &member)
}

// EditMessageText allows to edit text and game messages sent by the bot or via the bot (for inline bots)
func (b TelegramBot) EditMessageText(config EditMessageTextConfig) (message Message, err error) {
	return message, b.sendResuest("editMessageText", config, &message)
}

// EditMessageCaption allows to edit captions of messages sent by the bot or via the bot (for inline bots)
func (b TelegramBot) EditMessageCaption(config EditMessageCaptionConfig) (message Message, err error) {
	return message, b.sendResuest("editMessageCaption", config, &message)
}

// GetStickerSet allows to get a sticker set
func (b TelegramBot) GetStickerSet(config GetStickerSetConfig) (stickerSet StickerSet, err error) {
	return stickerSet, b.sendResuest("getStickerSet", config, &stickerSet)
}

// UploadStickerFile allows to upload a .png file with a sticker for
// later use in CreateNewStickerSet and AddStickerToSet methods
func (b TelegramBot) UploadStickerFile(config UploadStickerFileConfig) (file File, err error) {
	return file, b.sendResuest("uploadStickerFile", config, &file)
}

// CreateNewStickerSet allows to create new sticker set owned by a user
func (b TelegramBot) CreateNewStickerSet(config CreateNewStickerSetConfig) (success bool, err error) {
	return success, b.sendResuest("createNewStickerSet", config, &success)
}

// AddStickerToSet allows to add a new sticker to a set created by the bot
func (b TelegramBot) AddStickerToSet(config AddStickerToSetConfig) (success bool, err error) {
	return success, b.sendResuest("addStickerToSet", config, &success)
}

// SetStickerPositionInSet allows to move a sticker in a set created by the bot to a specific position
func (b TelegramBot) SetStickerPositionInSet(config SetStickerPositionInSetConfig) (success bool, err error) {
	return success, b.sendResuest("setStickerPositionInSet", config, &success)
}

// DeleteStickerFromSet allows to delete a sticker from a set created by the bot
func (b TelegramBot) DeleteStickerFromSet(config DeleteStickerFromSetConfig) (success bool, err error) {
	return success, b.sendResuest("deleteStickerFromSet", config, &success)
}

// AnswerInlineQuery allows to send answers to an inline query
func (b TelegramBot) AnswerInlineQuery(config AnswerInlineQueryConfig) (success bool, err error) {
	return success, b.sendResuest("answerInlineQuery", config, &success)
}

func (b TelegramBot) sendResuest(method string, paramsObject interface{}, t interface{}) error {
	return sendResuest(method, b.apiKey, paramsObject, &t)
}
<commit_msg>Fix synchronous webhook updates<commit_after>package tgbot

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

// TelegramBot allows to interact with Telegram Bot API
type TelegramBot struct {
	apiKey       string
	lastUpdateID int
}

// New returns a new TelegramBot instance
func New(apiKey string) (TelegramBot, error) {
	var err error
	var bot = TelegramBot{
		apiKey:       apiKey,
		lastUpdateID: -1,
	}

	_, err = bot.GetMe()
	if err != nil {
		return TelegramBot{}, err
	}

	_, err = bot.DeleteWebhook()
	if err != nil {
		return TelegramBot{}, err
	}

	return bot, nil
}

// PollConfig represents bot's polling configuration
type PollConfig struct {
	Callback func([]Update)
	Delay    int
}

// Poll starts updates polling
func (b *TelegramBot) Poll(config PollConfig) error {
	for {
		var updates, err = b.GetUpdates(GetUpdatesConfig{Offset: b.lastUpdateID + 1})
		if err == nil && len(updates) != 0 {
			b.lastUpdateID = updates[len(updates)-1].UpdateID
			go config.Callback(updates)
		}

		time.Sleep(time.Duration(config.Delay) * time.Millisecond)
	}
}

// ListenConfig represents bot's webhook configuration
type ListenConfig struct {
	Callback       func([]Update)
	Host           string
	Port           uint16
	KeyFilename    string
	CertFilename   string
	MaxConnections int
	AllowedUpdates []string
}

// Listen starts HTTPS server to receive updates
func (b *TelegramBot) Listen(config ListenConfig) error {
	http.HandleFunc("/"+b.apiKey, func(w http.ResponseWriter, req *http.Request) {
		var update Update
		err := json.NewDecoder(req.Body).Decode(&update)
		if err != nil {
			return
		}

		defer req.Body.Close()
		go config.Callback([]Update{update})
	})

	_, err := b.SetWebhook(SetWebhookConfig{
		URL:            fmt.Sprintf("https://%s:%d/%s", config.Host, config.Port, b.apiKey),
		Certificate:    FilePath(config.CertFilename),
		AllowedUpdates: config.AllowedUpdates,
	})

	if err != nil {
		return err
	}

	return http.ListenAndServeTLS(fmt.Sprintf(":%d", config.Port), config.CertFilename, config.KeyFilename, nil)
}

// GetMe returns basic information about the bot
func (b TelegramBot) GetMe() (user User, err error) {
	return user, b.sendResuest("getMe", nil, &user)
}

// GetUpdates allows to get new updates
func (b TelegramBot) GetUpdates(config GetUpdatesConfig) (updates []Update, err error) {
	return updates, b.sendResuest("getUpdates", config, &updates)
}

// SetWebhook used to specify url and receive incoming updates via an outgoing webhook
func (b TelegramBot) SetWebhook(config SetWebhookConfig) (success bool, err error) {
	return success, b.sendResuest("setWebhook", config, &success)
}

// DeleteWebhook used to remove webhook integration
func (b TelegramBot) DeleteWebhook() (success bool, err error) {
	return success, b.sendResuest("deleteWebhook", nil, &success)
}

// GetWebhookInfo user to get current webhook status
func (b TelegramBot) GetWebhookInfo() (info WebhookInfo, err error) {
	return info, b.sendResuest("getWebhookInfo", nil, &info)
}

// SendMessage sends text message
func (b TelegramBot) SendMessage(config SendMessageConfig) (message Message, err error) {
	return message, b.sendResuest("sendMessage", config, &message)
}

// ForwardMessage re-sends message of any type
func (b TelegramBot) ForwardMessage(config ForwardMessageConfig) (message Message, err error) {
	return message, b.sendResuest("forwardMessage", config, &message)
}

// SendPhoto sends photo message
func (b TelegramBot) SendPhoto(config SendPhotoConfig) (message Message, err error) {
	return message, b.sendResuest("sendPhoto", config, &message)
}

// SendAudio sends audio message
func (b TelegramBot) SendAudio(config SendAudioConfig) (message Message, err error) {
	return message, b.sendResuest("sendAudio", config, &message)
}

// SendDocument sends document message
func (b TelegramBot) SendDocument(config SendDocumentConfig) (message Message, err error) {
	return message, b.sendResuest("sendDocument", config, &message)
}

// SendVideo sends video message
func (b TelegramBot) SendVideo(config SendVideoConfig) (message Message, err error) {
	return message, b.sendResuest("sendVideo", config, &message)
}

// SendVoice sends voice note message
func (b TelegramBot) SendVoice(config SendVoiceConfig) (message Message, err error) {
	return message, b.sendResuest("sendVoice", config, &message)
}

// SendVideoNote sends video note message
func (b TelegramBot) SendVideoNote(config SendVideoNoteConfig) (message Message, err error) {
	return message, b.sendResuest("sendVideoNote", config, &message)
}

// SendLocation sends location message
func (b TelegramBot) SendLocation(config SendLocationConfig) (message Message, err error) {
	return message, b.sendResuest("sendLocation", config, &message)
}

// SendVenue sends information about a venue
func (b TelegramBot) SendVenue(config SendVenueConfig) (message Message, err error) {
	return message, b.sendResuest("sendVenue", config, &message)
}

// SendContact sends phone contact
func (b TelegramBot) SendContact(config SendContactConfig) (message Message, err error) {
	return message, b.sendResuest("sendContact", config, &message)
}

// SendSticker sends sticker
func (b TelegramBot) SendSticker(config SendStickerConfig) (message Message, err error) {
	return message, b.sendResuest("sendSticker", config, &message)
}

// SendChatAction sends phone contact
func (b TelegramBot) SendChatAction(config SendChatActionConfig) (success bool, err error) {
	return success, b.sendResuest("sendChatAction", config, &success)
}

// GetUserProfilePhotos returns user profile photos
func (b TelegramBot) GetUserProfilePhotos(config GetUserProfilePhotosConfig) (photos UserProfilePhotos, err error) {
	return photos, b.sendResuest("getUserProfilePhotos", config, &photos)
}

// GetFile allows to get basic info about a file and prepare it for downloading
func (b TelegramBot) GetFile(config GetFileConfig) (file File, err error) {
	return file, b.sendResuest("getFile", config, &file)
}

// KickChatMember allows to kick user from a group, a supergroup or a channel
func (b TelegramBot) KickChatMember(config KickChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("kickChatMember", config, &success)
}

// UnbanChatMember allows to unban user from a group, a supergroup or a channel
func (b TelegramBot) UnbanChatMember(config UnbanChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("unbanChatMember", config, &success)
}

// RestrictChatMember allows to restrict a user in a supergroup
func (b TelegramBot) RestrictChatMember(config RestrictChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("restrictChatMember", config, &success)
}

// PromoteChatMember allows to promote or demote a user in a supergroup or a channel
func (b TelegramBot) PromoteChatMember(config PromoteChatMemberConfig) (success bool, err error) {
	return success, b.sendResuest("promoteChatMember", config, &success)
}

// ExportChatInviteLink allows to export an invite link to a supergroup or a channel
func (b TelegramBot) ExportChatInviteLink(config ExportChatInviteLinkConfig) (link string, err error) {
	return link, b.sendResuest("exportChatInviteLink", config, &link)
}

// SetChatPhoto allows to set a new profile photo for the chat
func (b TelegramBot) SetChatPhoto(config SetChatPhotoConfig) (success bool, err error) {
	return success, b.sendResuest("setChatPhoto", config, &success)
}

// DeleteChatPhoto allows to delete a new profile photo for the chat
func (b TelegramBot) DeleteChatPhoto(config DeleteChatPhotoConfig) (success bool, err error) {
	return success, b.sendResuest("deleteChatPhoto", config, &success)
}

// SetChatTitle allows to change the title of a chat
func (b TelegramBot) SetChatTitle(config SetChatTitleConfig) (success bool, err error) {
	return success, b.sendResuest("setChatTitle", config, &success)
}

// SetChatDescription allows to change the description of a chat
func (b TelegramBot) SetChatDescription(config SetChatDescriptionConfig) (success bool, err error) {
	return success, b.sendResuest("setChatDescription", config, &success)
}

// PinChatMessage allows to pin a message in a supergroup
func (b TelegramBot) PinChatMessage(config PinChatMessageConfig) (success bool, err error) {
	return success, b.sendResuest("pinChatMessage", config, &success)
}

// UnpinChatMessage allows to unpin a message in a supergroup
func (b TelegramBot) UnpinChatMessage(config UnpinChatMessageConfig) (success bool, err error) {
	return success, b.sendResuest("unpinChatMessage", config, &success)
}

// LeaveChat allows to leave a group, supergroup or channel
func (b TelegramBot) LeaveChat(config LeaveChatConfig) (success bool, err error) {
	return success, b.sendResuest("leaveChat", config, &success)
}

// GetChat allows to get up to date information about the chat
func (b TelegramBot) GetChat(config GetChatConfig) (chat Chat, err error) {
	return chat, b.sendResuest("getChat", config, &chat)
}

// GetChatAdministrators allows to get a list of administrators in a chat
func (b TelegramBot) GetChatAdministrators(config GetChatAdministratorsConfig) (members []ChatMember, err error) {
	return members, b.sendResuest("getChatAdministrators", config, &members)
}

// GetChatMembersCount allows get the number of members in a chat
func (b TelegramBot) GetChatMembersCount(config GetChatMembersCountConfig) (count int, err error) {
	return count, b.sendResuest("getChatMembersCount", config, &count)
}

// GetChatMember allows to get information about a member of a chat
func (b TelegramBot) GetChatMember(config GetChatMemberConfig) (member ChatMember, err error) {
	return member, b.sendResuest("getChatMember", config, &member)
}

// EditMessageText allows to edit text and game messages sent by the bot or via the bot (for inline bots)
func (b TelegramBot) EditMessageText(config EditMessageTextConfig) (message Message, err error) {
	return message, b.sendResuest("editMessageText", config, &message)
}

// EditMessageCaption allows to edit captions of messages sent by the bot or via the bot (for inline bots)
func (b TelegramBot) EditMessageCaption(config EditMessageCaptionConfig) (message Message, err error) {
	return message, b.sendResuest("editMessageCaption", config, &message)
}

// GetStickerSet allows to get a sticker set
func (b TelegramBot) GetStickerSet(config GetStickerSetConfig) (stickerSet StickerSet, err error) {
	return stickerSet, b.sendResuest("getStickerSet", config, &stickerSet)
}

// UploadStickerFile allows to upload a .png file with a sticker for
// later use in CreateNewStickerSet and AddStickerToSet methods
func (b TelegramBot) UploadStickerFile(config UploadStickerFileConfig) (file File, err error) {
	return file, b.sendResuest("uploadStickerFile", config, &file)
}

// CreateNewStickerSet allows to create new sticker set owned by a user
func (b TelegramBot) CreateNewStickerSet(config CreateNewStickerSetConfig) (success bool, err error) {
	return success, b.sendResuest("createNewStickerSet", config, &success)
}

// AddStickerToSet allows to add a new sticker to a set created by the bot
func (b TelegramBot) AddStickerToSet(config AddStickerToSetConfig) (success bool, err error) {
	return success, b.sendResuest("addStickerToSet", config, &success)
}

// SetStickerPositionInSet allows to move a sticker in a set created by the bot to a specific position
func (b TelegramBot) SetStickerPositionInSet(config SetStickerPositionInSetConfig) (success bool, err error) {
	return success, b.sendResuest("setStickerPositionInSet", config, &success)
}

// DeleteStickerFromSet allows to delete a sticker from a set created by the bot
func (b TelegramBot) DeleteStickerFromSet(config DeleteStickerFromSetConfig) (success bool, err error) {
	return success, b.sendResuest("deleteStickerFromSet", config, &success)
}

// AnswerInlineQuery allows to send answers to an inline query
func (b TelegramBot) AnswerInlineQuery(config AnswerInlineQueryConfig) (success bool, err error) {
	return success, b.sendResuest("answerInlineQuery", config, &success)
}

func (b TelegramBot) sendResuest(method string, paramsObject interface{}, t interface{}) error {
	return sendResuest(method, b.apiKey, paramsObject, &t)
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"time"
//	"log"
//	"reflect"
)

const (
	alnum 			=	"abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789"
)

type Token struct {
	Key			string		// Key of the service
	Token		string
}

var	utokens		=	map[int32][]Token{}
var	tokens		=	map[string]int32{}
var timeouts	=	map[int64][]string{}

var chanmsg		chan Msg

type Msg interface {
	process()
}

func randomString(n int) string {
	buf := make([]byte, n)

	for i := 0; i < C.LenToken; i++ {
		buf[i] = alnum[rand.Intn(len(alnum))]
	}

	return string(buf)
}

func mkToken() string {
gen:
	token := randomString(C.LenToken)
	if _, exists := tokens[token]; exists {
		goto gen
	}

	return token
}

type NewMsg struct {
	uid		int32
	key		string
	answer	chan string
}

func (m NewMsg) process() {
	token := mkToken()
	// store token
	tokens[token] = m.uid
	utokens[m.uid] = append(utokens[m.uid], Token{ m.key, token })

	// setup timeout
	exptime := time.Now().Unix()+C.Timeout
	timeouts[exptime] = append(timeouts[exptime], token)

	// return value
	m.answer <- token
}

type RemoveMsg struct {
	token	string
}

func (m RemoveMsg) process() {
	if id := tokens[m.token]; id > 0 {
		n := len(utokens[id])
		for i := range utokens[id] {
			if utokens[id][i].Token == m.token {
				utokens[id][i]	= utokens[id][n-1]
				utokens[id]		= utokens[id][0:n-1]
				break
			}
		}
		delete(tokens, m.token)
	}
}

type CheckMsg struct {
	token	string
	answer	chan bool
}

func (m CheckMsg) process() {
	_, ok := tokens[m.token]
	m.answer <- ok
}

type UpdateMsg struct {
	token	string
	answer	chan string
}

func (m UpdateMsg) process() {
	// check old one
	id := tokens[m.token]
	if id == 0 { return }

	token := mkToken()

	delete(tokens, m.token)

	// create new one
	tokens[token] = id
	// update value
	for i := range utokens[id] {
		if utokens[id][i].Token == m.token {
			utokens[id][i].Token = token
			break
		}
	}

	// setup timeout
	exptime := time.Now().Unix()+C.Timeout
	timeouts[exptime] = append(timeouts[exptime], token)

	// return new one
	m.answer <- token
}

type AllMsg struct {
	token	string
	answer	chan []Token
}

func (m AllMsg) process() {
	m.answer <- utokens[tokens[m.token]]
}

type OwnMsg struct {
	token	string
	answer	chan int32
}

func (m OwnMsg) process() {
	m.answer <- tokens[m.token]
}

// background processes
func ProcessMsg() {
	chanmsg = make(chan Msg)
	for {
		m := <- chanmsg
//		log.Println("Process: ", reflect.TypeOf(m), ", ", m)
		m.process()
	}
}
func Timeouts() {
	for {
		time.Sleep(2*time.Second)
		now := time.Now().Unix()
		for date, toks := range timeouts {
			if date <= now {
				for _, token := range toks {
					RemoveToken(token)
				}
				delete(timeouts, date)
			}
		}
	}
}

// "API"
func NewToken(uid int32, key string) *Token {
	answer := make(chan string, 1)
	chanmsg <- NewMsg{ uid, key, answer }

	return &Token{ key, <- answer }
}

func CheckToken(token string) bool {
	answer := make(chan bool, 1)
	chanmsg <- CheckMsg{ token, answer }

	return <- answer
}

func UpdateToken(token string) string {
	answer := make(chan string, 1)
	chanmsg <- UpdateMsg { token, answer }

	return <- answer
}

func RemoveToken(token string) {
	chanmsg <- RemoveMsg{ token }
}

func AllTokens(token string) []Token {
	answer := make(chan []Token, 1)

	chanmsg <- AllMsg { token, answer }

	return <- answer
}

func OwnerToken(token string) int32 {
	answer := make(chan int32, 1)

	chanmsg  <- OwnMsg { token, answer }

	return <- answer
}
<commit_msg>bug: infinite loop when invalid token; thanks acieroid<commit_after>package main

import (
	"math/rand"
	"time"
//	"log"
//	"reflect"
)

const (
	alnum 			=	"abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789"
)

type Token struct {
	Key			string		// Key of the service
	Token		string
}

var	utokens		=	map[int32][]Token{}
var	tokens		=	map[string]int32{}
var timeouts	=	map[int64][]string{}

var chanmsg		chan Msg

type Msg interface {
	process()
}

func randomString(n int) string {
	buf := make([]byte, n)

	for i := 0; i < C.LenToken; i++ {
		buf[i] = alnum[rand.Intn(len(alnum))]
	}

	return string(buf)
}

func mkToken() string {
gen:
	token := randomString(C.LenToken)
	if _, exists := tokens[token]; exists {
		goto gen
	}

	return token
}

type NewMsg struct {
	uid		int32
	key		string
	answer	chan string
}

func (m NewMsg) process() {
	token := mkToken()
	// store token
	tokens[token] = m.uid
	utokens[m.uid] = append(utokens[m.uid], Token{ m.key, token })

	// setup timeout
	exptime := time.Now().Unix()+C.Timeout
	timeouts[exptime] = append(timeouts[exptime], token)

	// return value
	m.answer <- token
}

type RemoveMsg struct {
	token	string
}

func (m RemoveMsg) process() {
	if id := tokens[m.token]; id > 0 {
		n := len(utokens[id])
		for i := range utokens[id] {
			if utokens[id][i].Token == m.token {
				utokens[id][i]	= utokens[id][n-1]
				utokens[id]		= utokens[id][0:n-1]
				break
			}
		}
		delete(tokens, m.token)
	}
}

type CheckMsg struct {
	token	string
	answer	chan bool
}

func (m CheckMsg) process() {
	_, ok := tokens[m.token]
	m.answer <- ok
}

type UpdateMsg struct {
	token	string
	answer	chan string
}

func (m UpdateMsg) process() {
	// check old one
	id, ok := tokens[m.token]
	if !ok { m.answer <- ""; return }

	token := mkToken()

	delete(tokens, m.token)

	// create new one
	tokens[token] = id
	// update value
	for i := range utokens[id] {
		if utokens[id][i].Token == m.token {
			utokens[id][i].Token = token
			break
		}
	}

	// setup timeout
	exptime := time.Now().Unix()+C.Timeout
	timeouts[exptime] = append(timeouts[exptime], token)

	// return new one
	m.answer <- token
}

type AllMsg struct {
	token	string
	answer	chan []Token
}

func (m AllMsg) process() {
	m.answer <- utokens[tokens[m.token]]
}

type OwnMsg struct {
	token	string
	answer	chan int32
}

func (m OwnMsg) process() {
	m.answer <- tokens[m.token]
}

// background processes
func ProcessMsg() {
	chanmsg = make(chan Msg)
	for {
		m := <- chanmsg
//		log.Println("Process: ", reflect.TypeOf(m), ", ", m)
		m.process()
	}
}
func Timeouts() {
	for {
		time.Sleep(2*time.Second)
		now := time.Now().Unix()
		for date, toks := range timeouts {
			if date <= now {
				for _, token := range toks {
					RemoveToken(token)
				}
				delete(timeouts, date)
			}
		}
	}
}

// "API"
func NewToken(uid int32, key string) *Token {
	answer := make(chan string, 1)
	chanmsg <- NewMsg{ uid, key, answer }

	return &Token{ key, <- answer }
}

func CheckToken(token string) bool {
	answer := make(chan bool, 1)
	chanmsg <- CheckMsg{ token, answer }

	return <- answer
}

func UpdateToken(token string) string {
	answer := make(chan string, 1)
	chanmsg <- UpdateMsg { token, answer }

	return <- answer
}

func RemoveToken(token string) {
	chanmsg <- RemoveMsg{ token }
}

func AllTokens(token string) []Token {
	answer := make(chan []Token, 1)

	chanmsg <- AllMsg { token, answer }

	return <- answer
}

func OwnerToken(token string) int32 {
	answer := make(chan int32, 1)

	chanmsg  <- OwnMsg { token, answer }

	return <- answer
}
<|endoftext|>


<commit_before>// Copyright 2014 The oauth2 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 oauth2

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

	"github.com/cloudflare/oauth2/internal"
	"golang.org/x/net/context"
)

// expiryDelta determines how earlier a token should be considered
// expired than its actual expiration time. It is used to avoid late
// expirations due to client-server time mismatches.
const expiryDelta = 10 * time.Second

// Token represents the crendentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// Most users of this package should not access fields of Token
// directly. They're exported mostly for use by related packages
// implementing derivative OAuth2 flows.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string `json:"access_token"`

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string `json:"token_type,omitempty"`

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string `json:"refresh_token,omitempty"`

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time `json:"expiry,omitempty"`

	// raw optionally contains extra metadata from the server
	// when updating a token.
	raw interface{}
}

// Type returns t.TokenType if non-empty, else "Bearer".
func (t *Token) Type() string {
	if strings.EqualFold(t.TokenType, "bearer") {
		return "Bearer"
	}
	if strings.EqualFold(t.TokenType, "mac") {
		return "MAC"
	}
	if strings.EqualFold(t.TokenType, "basic") {
		return "Basic"
	}
	if t.TokenType != "" {
		return t.TokenType
	}
	return "Bearer"
}

// SetAuthHeader sets the Authorization header to r using the access
// token in t.
//
// This method is unnecessary when using Transport or an HTTP Client
// returned by this package.
func (t *Token) SetAuthHeader(r *http.Request) {
	r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
}

// WithExtra returns a new Token that's a clone of t, but using the
// provided raw extra map. This is only intended for use by packages
// implementing derivative OAuth2 flows.
func (t *Token) WithExtra(extra interface{}) *Token {
	t2 := new(Token)
	*t2 = *t
	t2.raw = extra
	return t2
}

// Extra returns an extra field.
// Extra fields are key-value pairs returned by the server as a
// part of the token retrieval response.
func (t *Token) Extra(key string) interface{} {
	if vals, ok := t.raw.(url.Values); ok {
		// TODO(jbd): Cast numeric values to int64 or float64.
		return vals.Get(key)
	}
	if raw, ok := t.raw.(map[string]interface{}); ok {
		return raw[key]
	}
	return nil
}

// expired reports whether the token is expired.
// t must be non-nil.
func (t *Token) expired() bool {
	if t.Expiry.IsZero() {
		return false
	}
	return t.Expiry.Add(-expiryDelta).Before(time.Now())
}

// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
func (t *Token) Valid() bool {
	return t != nil && t.AccessToken != "" && !t.expired()
}

// tokenFromInternal maps an *internal.Token struct into
// a *Token struct.
func tokenFromInternal(t *internal.Token) *Token {
	if t == nil {
		return nil
	}
	return &Token{
		AccessToken:  t.AccessToken,
		TokenType:    t.TokenType,
		RefreshToken: t.RefreshToken,
		Expiry:       t.Expiry,
		raw:          t.Raw,
	}
}

// retrieveToken takes a *Config and uses that to retrieve an *internal.Token.
// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along
// with an error..
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
	tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
	if err != nil {
		return nil, err
	}
	return tokenFromInternal(tk), nil
}
<commit_msg>Add method which returns all extra fields on a token as a map<commit_after>// Copyright 2014 The oauth2 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 oauth2

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

	"github.com/cloudflare/oauth2/internal"
	"golang.org/x/net/context"
)

// expiryDelta determines how earlier a token should be considered
// expired than its actual expiration time. It is used to avoid late
// expirations due to client-server time mismatches.
const expiryDelta = 10 * time.Second

// These are the fields which we normally expect to be in a token,
// so we don't include them with requests for the Extra fields.
var OAUTH_FIELDS = []string{"access_token", "token_type", "refresh_token", "expires_in", "expires"}

// Token represents the crendentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// Most users of this package should not access fields of Token
// directly. They're exported mostly for use by related packages
// implementing derivative OAuth2 flows.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string `json:"access_token"`

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string `json:"token_type,omitempty"`

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string `json:"refresh_token,omitempty"`

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time `json:"expiry,omitempty"`

	// raw optionally contains extra metadata from the server
	// when updating a token.
	raw interface{}
}

// Type returns t.TokenType if non-empty, else "Bearer".
func (t *Token) Type() string {
	if strings.EqualFold(t.TokenType, "bearer") {
		return "Bearer"
	}
	if strings.EqualFold(t.TokenType, "mac") {
		return "MAC"
	}
	if strings.EqualFold(t.TokenType, "basic") {
		return "Basic"
	}
	if t.TokenType != "" {
		return t.TokenType
	}
	return "Bearer"
}

// SetAuthHeader sets the Authorization header to r using the access
// token in t.
//
// This method is unnecessary when using Transport or an HTTP Client
// returned by this package.
func (t *Token) SetAuthHeader(r *http.Request) {
	r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
}

// WithExtra returns a new Token that's a clone of t, but using the
// provided raw extra map. This is only intended for use by packages
// implementing derivative OAuth2 flows.
func (t *Token) WithExtra(extra interface{}) *Token {
	t2 := new(Token)
	*t2 = *t
	t2.raw = extra
	return t2
}

// Extra returns an extra field.
// Extra fields are key-value pairs returned by the server as a
// part of the token retrieval response.
func (t *Token) Extra(key string) interface{} {
	if vals, ok := t.raw.(url.Values); ok {
		// TODO(jbd): Cast numeric values to int64 or float64.
		return vals.Get(key)
	}
	if raw, ok := t.raw.(map[string]interface{}); ok {
		return raw[key]
	}
	return nil
}

func (t *Token) ExtraAsMap() map[string]interface{} {
	// The extra fields in a token ('Raw') can be either a map or URL values depending on the
	// encoding of the token.

	extra := make(map[string]interface{})

	asValues, ok := t.raw.(url.Values)
	if ok {
		for key, values := range asValues {
			extra[key] = values[0]
		}
	} else {
		asMap, ok := t.raw.(map[string]interface{})
		if ok {
			extra = asMap
		}
	}

	for key := range extra {
		for _, field := range OAUTH_FIELDS {
			if field == key {
				delete(extra, key)
				break
			}
		}
	}

	return extra
}

// expired reports whether the token is expired.
// t must be non-nil.
func (t *Token) expired() bool {
	if t.Expiry.IsZero() {
		return false
	}
	return t.Expiry.Add(-expiryDelta).Before(time.Now())
}

// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
func (t *Token) Valid() bool {
	return t != nil && t.AccessToken != "" && !t.expired()
}

// tokenFromInternal maps an *internal.Token struct into
// a *Token struct.
func tokenFromInternal(t *internal.Token) *Token {
	if t == nil {
		return nil
	}
	return &Token{
		AccessToken:  t.AccessToken,
		TokenType:    t.TokenType,
		RefreshToken: t.RefreshToken,
		Expiry:       t.Expiry,
		raw:          t.Raw,
	}
}

// retrieveToken takes a *Config and uses that to retrieve an *internal.Token.
// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along
// with an error..
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
	tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
	if err != nil {
		return nil, err
	}
	return tokenFromInternal(tk), nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"text/template"
	"time"

	"github.com/citadel/citadel"
	"github.com/citadel/citadel/cluster"
	"github.com/citadel/citadel/scheduler"
	"github.com/ehazlett/interlock"
	"github.com/shipyard/shipyard/client"
)

const (
	haproxyTmpl = `# managed by interlock
global
    {{ if .Config.SyslogAddr }}log {{ .Config.SyslogAddr }} local0
    log-send-hostname{{ end }}
    maxconn {{ .Config.MaxConn }}
    pidfile {{ .Config.PidPath }}

defaults
    mode http
    retries 3
    option redispatch
    option httplog
    option dontlognull
    timeout connect {{ .Config.ConnectTimeout }}
    timeout client {{ .Config.ClientTimeout }}
    timeout server {{ .Config.ServerTimeout }}

frontend http-default
    bind *:{{ .Config.Port }}
    {{ if .Config.SSLCert }}bind *:{{ .Config.SSLPort }} ssl crt {{ .Config.SSLCert }} {{ .Config.SSLOpts }}{{ end }}
    monitor-uri /haproxy?monitor
    {{ if .Config.StatsUser }}stats realm Stats
    stats auth {{ .Config.StatsUser }}:{{ .Config.StatsPassword }}{{ end }}
    stats enable
    stats uri /haproxy?stats
    stats refresh 5s
    {{ range $host := .Hosts }}acl is_{{ $host.Name }} hdr_beg(host) {{ $host.Domain }}
    use_backend {{ $host.Name }} if is_{{ $host.Name }}
    {{ end }}
{{ range $host := .Hosts }}backend {{ $host.Name }}
    http-response add-header X-Request-Start %Ts.%ms
    balance roundrobin
    option forwardfor
    {{ range $option := $host.BackendOptions }}option {{ $option }}
    {{ end }}
    {{ if $host.Check }}option {{ $host.Check }}{{ end }}
    {{ if $host.SSLOnly }}redirect scheme https if !{ ssl_fc  }{{ end }}
    {{ range $i,$up := $host.Upstreams }}server {{ $host.Name }}_{{ $i }} {{ $up.Addr }} check inter {{ $up.CheckInterval }}
    {{ end }}
{{ end }}`
)

type (
	Manager struct {
		mux      sync.Mutex
		config   *interlock.Config
		engines  []*citadel.Engine
		cluster  *cluster.Cluster
		proxyCmd *exec.Cmd
	}
)

func NewManager(cfg *interlock.Config) (*Manager, error) {
	engines := []*citadel.Engine{}
	for _, e := range cfg.InterlockEngines {
		engines = append(engines, e.Engine)
	}
	m := &Manager{
		config:  cfg,
		engines: engines,
	}
	if err := m.init(); err != nil {
		return nil, err
	}
	return m, nil
}

func (m *Manager) init() error {
	var engines []*citadel.Engine
	if m.config.ShipyardUrl != "" {
		cfg := &client.ShipyardConfig{
			Url:        m.config.ShipyardUrl,
			ServiceKey: m.config.ShipyardServiceKey,
		}
		mgr := client.NewManager(cfg)
		eng, err := mgr.Engines()
		if err != nil {
			return err
		}
		for _, e := range eng {
			engines = append(engines, e.Engine)
		}
	} else {
		engines = m.engines
	}
	for _, e := range engines {
		if err := e.Connect(nil); err != nil {
			return err
		}
		logger.Infof("loaded engine: %s", e.ID)
	}
	c, err := cluster.New(scheduler.NewResourceManager(), engines...)
	if err != nil {
		return err
	}
	m.cluster = c
	// register handler
	if err := m.cluster.Events(&EventHandler{Manager: m}); err != nil {
		return err
	}
	return nil
}

func (m *Manager) writeConfig(config *interlock.ProxyConfig) error {
	m.mux.Lock()
	defer m.mux.Unlock()
	f, err := os.OpenFile(m.config.ProxyConfigPath, os.O_WRONLY|os.O_TRUNC, 0664)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		ff, fErr := os.Create(m.config.ProxyConfigPath)
		defer ff.Close()
		if fErr != nil {
			return fErr
		}
		f = ff
	}
	defer f.Close()
	t := template.New("haproxy")
	tmpl, err := t.Parse(haproxyTmpl)
	if err != nil {
		return err
	}
	var c bytes.Buffer
	if err := tmpl.Execute(&c, config); err != nil {
		return err
	}
	_, fErr := f.Write(c.Bytes())
	if fErr != nil {
		return fErr
	}
	f.Sync()
	return nil
}

func (m *Manager) GenerateProxyConfig(isKillEvent bool) (*interlock.ProxyConfig, error) {
	containers, err := m.cluster.ListContainers(false)
	if err != nil {
		return nil, err
	}
	var hosts []*interlock.Host
	proxyUpstreams := map[string][]*interlock.Upstream{}
	hostChecks := map[string]string{}
	hostBackendOptions := map[string][]string{}
	hostSSLOnly := map[string]bool{}
	for _, cnt := range containers {
		cntId := cnt.ID[:12]
		// load interlock data
		env := cnt.Image.Environment
		interlockData := &interlock.InterlockData{}
		if key, ok := env["INTERLOCK_DATA"]; ok {
			b := bytes.NewBufferString(key)
			if err := json.NewDecoder(b).Decode(&interlockData); err != nil {
				logger.Warnf("%s: unable to parse interlock data: %s", cntId, err)
			}
		}
		hostname := cnt.Image.Hostname
		domain := cnt.Image.Domainname
		if interlockData.Hostname != "" {
			hostname = interlockData.Hostname
		}
		if interlockData.Domain != "" {
			domain = interlockData.Domain
		}
		if domain == "" {
			continue
		}
		if hostname != domain && hostname != "" {
			domain = fmt.Sprintf("%s.%s", hostname, domain)
		}
		if interlockData.Check != "" {
			if val, ok := hostChecks[domain]; ok {
				// check existing host check for different values
				if val != interlockData.Check {
					logger.Warnf("conflicting check specified for %s", domain)
				}
			} else {
				hostChecks[domain] = interlockData.Check
				logger.Infof("using custom check for %s: %s", domain, interlockData.Check)
			}
		}
		checkInterval := 5000
		if interlockData.CheckInterval != 0 {
			checkInterval = interlockData.CheckInterval
			logger.Infof("using custom check interval for %s: %d", domain, checkInterval)
		}
		if len(interlockData.BackendOptions) > 0 {
			hostBackendOptions[domain] = interlockData.BackendOptions
			logger.Infof("using backend options for %s: %s", domain, strings.Join(interlockData.BackendOptions, ","))
		}
		hostSSLOnly[domain] = false
		if interlockData.SSLOnly {
			logger.Infof("configuring ssl redirect for %s", domain)
			hostSSLOnly[domain] = true
		}
		hostAddrUrl, err := url.Parse(cnt.Engine.Addr)
		if err != nil {
			logger.Warnf("%s: unable to parse engine addr: %s", cntId, err)
			continue
		}
		host := hostAddrUrl.Host
		hostParts := strings.Split(hostAddrUrl.Host, ":")
		if len(hostParts) != 1 {
			host = hostParts[0]
		}
		if len(cnt.Ports) == 0 {
			logger.Warnf("%s: no ports exposed", cntId)
			continue
		}
		portDef := cnt.Ports[0]
		addr := fmt.Sprintf("%s:%d", host, portDef.Port)
		if interlockData.Port != 0 {
			for _, p := range cnt.Ports {
				if p.ContainerPort == interlockData.Port {
					addr = fmt.Sprintf("%s:%d", host, p.Port)
				}
			}
		}
		up := &interlock.Upstream{
			Addr:          addr,
			CheckInterval: checkInterval,
		}
		for _, alias := range interlockData.AliasDomains {
			logger.Infof("adding alias %s for %s", alias, cntId)
			proxyUpstreams[alias] = append(proxyUpstreams[alias], up)
		}
		proxyUpstreams[domain] = append(proxyUpstreams[domain], up)
		if !isKillEvent && interlockData.Warm {
			logger.Infof("warming %s: %s", cntId, addr)
			http.Get(fmt.Sprintf("http://%s", addr))
		}

	}
	for k, v := range proxyUpstreams {
		name := strings.Replace(k, ".", "_", -1)
		host := &interlock.Host{
			Name:           name,
			Domain:         k,
			Upstreams:      v,
			Check:          hostChecks[k],
			BackendOptions: hostBackendOptions[k],
			SSLOnly:        hostSSLOnly[k],
		}
		logger.Infof("adding host name=%s domain=%s", host.Name, host.Domain)
		hosts = append(hosts, host)
	}
	// generate config
	cfg := &interlock.ProxyConfig{
		Hosts:  hosts,
		Config: m.config,
	}
	return cfg, nil
}

func (m *Manager) UpdateConfig(e *citadel.Event) error {
	isKillEvent := false
	if e != nil && e.Type == "kill" {
		isKillEvent = true
	}
	cfg, err := m.GenerateProxyConfig(isKillEvent)
	if err != nil {
		return err
	}
	if err := m.writeConfig(cfg); err != nil {
		return err
	}
	return nil
}

func (m *Manager) getProxyPid() (int, error) {
	f, err := ioutil.ReadFile(m.config.PidPath)
	if err != nil {
		return -1, err
	}
	buf := bytes.NewBuffer(f)
	p := buf.String()
	p = strings.TrimSpace(p)
	pid, err := strconv.Atoi(p)
	if err != nil {
		return -1, err
	}
	return pid, nil
}

func (m *Manager) Reload() error {
	args := []string{"-D", "-f", m.config.ProxyConfigPath, "-p", m.config.PidPath, "-sf"}
	if m.proxyCmd != nil {
		p, err := m.getProxyPid()
		if err != nil {
			logger.Error(err)
		}
		pid := strconv.Itoa(p)
		args = append(args, pid)
	}
	cmd := exec.Command("haproxy", args...)
	if err := cmd.Run(); err != nil {
		return err
	}
	m.proxyCmd = cmd
	logger.Info("reloaded proxy")
	return nil
}

func (m *Manager) Run() error {
	if err := m.UpdateConfig(nil); err != nil {
		return err
	}
	m.Reload()
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	signal.Notify(ch, syscall.SIGTERM)
	go func() {
		<-ch
		if m.proxyCmd != nil {
			pid, err := m.getProxyPid()
			if err != nil {
				logger.Fatal(err)
			}
			syscall.Kill(pid, syscall.SIGTERM)
		}
		os.Exit(1)
	}()

	for {
		time.Sleep(1 * time.Second)
	}
}
<commit_msg>move forwardfor to defaults; add http-server-close<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"text/template"
	"time"

	"github.com/citadel/citadel"
	"github.com/citadel/citadel/cluster"
	"github.com/citadel/citadel/scheduler"
	"github.com/ehazlett/interlock"
	"github.com/shipyard/shipyard/client"
)

const (
	haproxyTmpl = `# managed by interlock
global
    {{ if .Config.SyslogAddr }}log {{ .Config.SyslogAddr }} local0
    log-send-hostname{{ end }}
    maxconn {{ .Config.MaxConn }}
    pidfile {{ .Config.PidPath }}

defaults
    mode http
    retries 3
    option redispatch
    option httplog
    option dontlognull
    option http-server-close
    option forwardfor
    timeout connect {{ .Config.ConnectTimeout }}
    timeout client {{ .Config.ClientTimeout }}
    timeout server {{ .Config.ServerTimeout }}

frontend http-default
    bind *:{{ .Config.Port }}
    {{ if .Config.SSLCert }}bind *:{{ .Config.SSLPort }} ssl crt {{ .Config.SSLCert }} {{ .Config.SSLOpts }}{{ end }}
    monitor-uri /haproxy?monitor
    {{ if .Config.StatsUser }}stats realm Stats
    stats auth {{ .Config.StatsUser }}:{{ .Config.StatsPassword }}{{ end }}
    stats enable
    stats uri /haproxy?stats
    stats refresh 5s
    {{ range $host := .Hosts }}acl is_{{ $host.Name }} hdr_beg(host) {{ $host.Domain }}
    use_backend {{ $host.Name }} if is_{{ $host.Name }}
    {{ end }}
{{ range $host := .Hosts }}backend {{ $host.Name }}
    http-response add-header X-Request-Start %Ts.%ms
    balance roundrobin
    {{ range $option := $host.BackendOptions }}option {{ $option }}
    {{ end }}
    {{ if $host.Check }}option {{ $host.Check }}{{ end }}
    {{ if $host.SSLOnly }}redirect scheme https if !{ ssl_fc  }{{ end }}
    {{ range $i,$up := $host.Upstreams }}server {{ $host.Name }}_{{ $i }} {{ $up.Addr }} check inter {{ $up.CheckInterval }}
    {{ end }}
{{ end }}`
)

type (
	Manager struct {
		mux      sync.Mutex
		config   *interlock.Config
		engines  []*citadel.Engine
		cluster  *cluster.Cluster
		proxyCmd *exec.Cmd
	}
)

func NewManager(cfg *interlock.Config) (*Manager, error) {
	engines := []*citadel.Engine{}
	for _, e := range cfg.InterlockEngines {
		engines = append(engines, e.Engine)
	}
	m := &Manager{
		config:  cfg,
		engines: engines,
	}
	if err := m.init(); err != nil {
		return nil, err
	}
	return m, nil
}

func (m *Manager) init() error {
	var engines []*citadel.Engine
	if m.config.ShipyardUrl != "" {
		cfg := &client.ShipyardConfig{
			Url:        m.config.ShipyardUrl,
			ServiceKey: m.config.ShipyardServiceKey,
		}
		mgr := client.NewManager(cfg)
		eng, err := mgr.Engines()
		if err != nil {
			return err
		}
		for _, e := range eng {
			engines = append(engines, e.Engine)
		}
	} else {
		engines = m.engines
	}
	for _, e := range engines {
		if err := e.Connect(nil); err != nil {
			return err
		}
		logger.Infof("loaded engine: %s", e.ID)
	}
	c, err := cluster.New(scheduler.NewResourceManager(), engines...)
	if err != nil {
		return err
	}
	m.cluster = c
	// register handler
	if err := m.cluster.Events(&EventHandler{Manager: m}); err != nil {
		return err
	}
	return nil
}

func (m *Manager) writeConfig(config *interlock.ProxyConfig) error {
	m.mux.Lock()
	defer m.mux.Unlock()
	f, err := os.OpenFile(m.config.ProxyConfigPath, os.O_WRONLY|os.O_TRUNC, 0664)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		ff, fErr := os.Create(m.config.ProxyConfigPath)
		defer ff.Close()
		if fErr != nil {
			return fErr
		}
		f = ff
	}
	defer f.Close()
	t := template.New("haproxy")
	tmpl, err := t.Parse(haproxyTmpl)
	if err != nil {
		return err
	}
	var c bytes.Buffer
	if err := tmpl.Execute(&c, config); err != nil {
		return err
	}
	_, fErr := f.Write(c.Bytes())
	if fErr != nil {
		return fErr
	}
	f.Sync()
	return nil
}

func (m *Manager) GenerateProxyConfig(isKillEvent bool) (*interlock.ProxyConfig, error) {
	containers, err := m.cluster.ListContainers(false)
	if err != nil {
		return nil, err
	}
	var hosts []*interlock.Host
	proxyUpstreams := map[string][]*interlock.Upstream{}
	hostChecks := map[string]string{}
	hostBackendOptions := map[string][]string{}
	hostSSLOnly := map[string]bool{}
	for _, cnt := range containers {
		cntId := cnt.ID[:12]
		// load interlock data
		env := cnt.Image.Environment
		interlockData := &interlock.InterlockData{}
		if key, ok := env["INTERLOCK_DATA"]; ok {
			b := bytes.NewBufferString(key)
			if err := json.NewDecoder(b).Decode(&interlockData); err != nil {
				logger.Warnf("%s: unable to parse interlock data: %s", cntId, err)
			}
		}
		hostname := cnt.Image.Hostname
		domain := cnt.Image.Domainname
		if interlockData.Hostname != "" {
			hostname = interlockData.Hostname
		}
		if interlockData.Domain != "" {
			domain = interlockData.Domain
		}
		if domain == "" {
			continue
		}
		if hostname != domain && hostname != "" {
			domain = fmt.Sprintf("%s.%s", hostname, domain)
		}
		if interlockData.Check != "" {
			if val, ok := hostChecks[domain]; ok {
				// check existing host check for different values
				if val != interlockData.Check {
					logger.Warnf("conflicting check specified for %s", domain)
				}
			} else {
				hostChecks[domain] = interlockData.Check
				logger.Infof("using custom check for %s: %s", domain, interlockData.Check)
			}
		}
		checkInterval := 5000
		if interlockData.CheckInterval != 0 {
			checkInterval = interlockData.CheckInterval
			logger.Infof("using custom check interval for %s: %d", domain, checkInterval)
		}
		if len(interlockData.BackendOptions) > 0 {
			hostBackendOptions[domain] = interlockData.BackendOptions
			logger.Infof("using backend options for %s: %s", domain, strings.Join(interlockData.BackendOptions, ","))
		}
		hostSSLOnly[domain] = false
		if interlockData.SSLOnly {
			logger.Infof("configuring ssl redirect for %s", domain)
			hostSSLOnly[domain] = true
		}
		hostAddrUrl, err := url.Parse(cnt.Engine.Addr)
		if err != nil {
			logger.Warnf("%s: unable to parse engine addr: %s", cntId, err)
			continue
		}
		host := hostAddrUrl.Host
		hostParts := strings.Split(hostAddrUrl.Host, ":")
		if len(hostParts) != 1 {
			host = hostParts[0]
		}
		if len(cnt.Ports) == 0 {
			logger.Warnf("%s: no ports exposed", cntId)
			continue
		}
		portDef := cnt.Ports[0]
		addr := fmt.Sprintf("%s:%d", host, portDef.Port)
		if interlockData.Port != 0 {
			for _, p := range cnt.Ports {
				if p.ContainerPort == interlockData.Port {
					addr = fmt.Sprintf("%s:%d", host, p.Port)
				}
			}
		}
		up := &interlock.Upstream{
			Addr:          addr,
			CheckInterval: checkInterval,
		}
		for _, alias := range interlockData.AliasDomains {
			logger.Infof("adding alias %s for %s", alias, cntId)
			proxyUpstreams[alias] = append(proxyUpstreams[alias], up)
		}
		proxyUpstreams[domain] = append(proxyUpstreams[domain], up)
		if !isKillEvent && interlockData.Warm {
			logger.Infof("warming %s: %s", cntId, addr)
			http.Get(fmt.Sprintf("http://%s", addr))
		}

	}
	for k, v := range proxyUpstreams {
		name := strings.Replace(k, ".", "_", -1)
		host := &interlock.Host{
			Name:           name,
			Domain:         k,
			Upstreams:      v,
			Check:          hostChecks[k],
			BackendOptions: hostBackendOptions[k],
			SSLOnly:        hostSSLOnly[k],
		}
		logger.Infof("adding host name=%s domain=%s", host.Name, host.Domain)
		hosts = append(hosts, host)
	}
	// generate config
	cfg := &interlock.ProxyConfig{
		Hosts:  hosts,
		Config: m.config,
	}
	return cfg, nil
}

func (m *Manager) UpdateConfig(e *citadel.Event) error {
	isKillEvent := false
	if e != nil && e.Type == "kill" {
		isKillEvent = true
	}
	cfg, err := m.GenerateProxyConfig(isKillEvent)
	if err != nil {
		return err
	}
	if err := m.writeConfig(cfg); err != nil {
		return err
	}
	return nil
}

func (m *Manager) getProxyPid() (int, error) {
	f, err := ioutil.ReadFile(m.config.PidPath)
	if err != nil {
		return -1, err
	}
	buf := bytes.NewBuffer(f)
	p := buf.String()
	p = strings.TrimSpace(p)
	pid, err := strconv.Atoi(p)
	if err != nil {
		return -1, err
	}
	return pid, nil
}

func (m *Manager) Reload() error {
	args := []string{"-D", "-f", m.config.ProxyConfigPath, "-p", m.config.PidPath, "-sf"}
	if m.proxyCmd != nil {
		p, err := m.getProxyPid()
		if err != nil {
			logger.Error(err)
		}
		pid := strconv.Itoa(p)
		args = append(args, pid)
	}
	cmd := exec.Command("haproxy", args...)
	if err := cmd.Run(); err != nil {
		return err
	}
	m.proxyCmd = cmd
	logger.Info("reloaded proxy")
	return nil
}

func (m *Manager) Run() error {
	if err := m.UpdateConfig(nil); err != nil {
		return err
	}
	m.Reload()
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	signal.Notify(ch, syscall.SIGTERM)
	go func() {
		<-ch
		if m.proxyCmd != nil {
			pid, err := m.getProxyPid()
			if err != nil {
				logger.Fatal(err)
			}
			syscall.Kill(pid, syscall.SIGTERM)
		}
		os.Exit(1)
	}()

	for {
		time.Sleep(1 * time.Second)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/fabiofalci/sconsify/sconsify"
	"github.com/fabiofalci/sconsify/spotify"
	"github.com/fabiofalci/sconsify/ui"
	"github.com/howeyc/gopass"
)

var version string
var commit string
var buildDate string

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

func main() {
	sconsify.ProcessSconsifyrc()

	providedUsername := flag.String("username", "", "Spotify username.")
	providedUi := flag.Bool("ui", true, "Run Sconsify with Console User Interface. If false then no User Interface will be presented and it'll only random between Playlists.")
	providedPlaylists := flag.String("playlists", "", "Select just some Playlists to play. Comma separated list.")
	providedNoUiSilent := flag.Bool("noui-silent", false, "Silent mode when no UI is used.")
	providedNoUiRepeatOn := flag.Bool("noui-repeat-on", true, "Play your playlist and repeat it after the last track.")
	providedNoUiRandom := flag.Bool("noui-random", true, "Random between tracks or follow playlist order.")
	providedDebug := flag.Bool("debug", false, "Enable debug mode.")
	askingVersion := flag.Bool("version", false, "Print version.")
	flag.Parse()

	if *askingVersion {
		fmt.Println("Version: " + version)
		fmt.Println("Git commit: " + commit)
		fmt.Println("Build date: " + buildDate)
		os.Exit(0)
	}

	if *providedDebug {
		sconsify.InitialiseLogger()
		defer sconsify.CloseLogger()
	}

	fmt.Println("Sconsify - your awesome Spotify music service in a text-mode interface.")
	username, pass := credentials(providedUsername)
	events := sconsify.InitialiseEvents()

	go spotify.Initialise(username, pass, events, providedPlaylists)

	if *providedUi {
		ui := ui.InitialiseConsoleUserInterface(events)
		sconsify.StartMainLoop(events, ui, false)
	} else {
		var output ui.Printer
		if *providedNoUiSilent {
			output = new(ui.SilentPrinter)
		}
		ui := ui.InitialiseNoUserInterface(events, output, providedNoUiRepeatOn, providedNoUiRandom)
		sconsify.StartMainLoop(events, ui, true)
	}
}

func credentials(providedUsername *string) (string, []byte) {
	username := ""
	if *providedUsername == "" {
		fmt.Print("Username: ")
		reader := bufio.NewReader(os.Stdin)
		username, _ = reader.ReadString('\n')
	} else {
		username = *providedUsername
		fmt.Println("Provided username: " + username)
	}
	return strings.Trim(username, " \n\r"), getPassword()
}

func getPassword() []byte {
	passFromEnv := os.Getenv("SCONSIFY_PASSWORD")
	if passFromEnv != "" {
		fmt.Println("Reading password from environment variable SCONSIFY_PASSWORD.")
		return []byte(passFromEnv)
	}
	fmt.Print("Password: ")
	return gopass.GetPasswdMasked()
}
<commit_msg>Make premium account requirement explicit<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/fabiofalci/sconsify/sconsify"
	"github.com/fabiofalci/sconsify/spotify"
	"github.com/fabiofalci/sconsify/ui"
	"github.com/howeyc/gopass"
)

var version string
var commit string
var buildDate string

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

func main() {
	sconsify.ProcessSconsifyrc()

	providedUsername := flag.String("username", "", "Spotify username.")
	providedUi := flag.Bool("ui", true, "Run Sconsify with Console User Interface. If false then no User Interface will be presented and it'll only random between Playlists.")
	providedPlaylists := flag.String("playlists", "", "Select just some Playlists to play. Comma separated list.")
	providedNoUiSilent := flag.Bool("noui-silent", false, "Silent mode when no UI is used.")
	providedNoUiRepeatOn := flag.Bool("noui-repeat-on", true, "Play your playlist and repeat it after the last track.")
	providedNoUiRandom := flag.Bool("noui-random", true, "Random between tracks or follow playlist order.")
	providedDebug := flag.Bool("debug", false, "Enable debug mode.")
	askingVersion := flag.Bool("version", false, "Print version.")
	flag.Parse()

	if *askingVersion {
		fmt.Println("Version: " + version)
		fmt.Println("Git commit: " + commit)
		fmt.Println("Build date: " + buildDate)
		os.Exit(0)
	}

	if *providedDebug {
		sconsify.InitialiseLogger()
		defer sconsify.CloseLogger()
	}

	fmt.Println("Sconsify - your awesome Spotify music service in a text-mode interface.")
	username, pass := credentials(providedUsername)
	events := sconsify.InitialiseEvents()

	go spotify.Initialise(username, pass, events, providedPlaylists)

	if *providedUi {
		ui := ui.InitialiseConsoleUserInterface(events)
		sconsify.StartMainLoop(events, ui, false)
	} else {
		var output ui.Printer
		if *providedNoUiSilent {
			output = new(ui.SilentPrinter)
		}
		ui := ui.InitialiseNoUserInterface(events, output, providedNoUiRepeatOn, providedNoUiRandom)
		sconsify.StartMainLoop(events, ui, true)
	}
}

func credentials(providedUsername *string) (string, []byte) {
	username := ""
	if *providedUsername == "" {
		fmt.Print("Premium account username: ")
		reader := bufio.NewReader(os.Stdin)
		username, _ = reader.ReadString('\n')
	} else {
		username = *providedUsername
		fmt.Println("Provided username: " + username)
	}
	return strings.Trim(username, " \n\r"), getPassword()
}

func getPassword() []byte {
	passFromEnv := os.Getenv("SCONSIFY_PASSWORD")
	if passFromEnv != "" {
		fmt.Println("Reading password from environment variable SCONSIFY_PASSWORD.")
		return []byte(passFromEnv)
	}
	fmt.Print("Password: ")
	return gopass.GetPasswdMasked()
}
<|endoftext|>


<commit_before>/*
Tiny web server to handle slack outgoing webhook and push the data to resque
*/
package main

import (
	"fmt"
	"net/http"
	"net/url"
	"encoding/json"
	"strings"
	"github.com/fzzy/radix/redis"
)

// some default global variable
var (
	PORT string = ":8765"
	API_PATH string = "/api"
	REDIS string = "localhost:6379"
	CLASS string = "SlackOPS"
	QUEUE string = "slackops"
)

func main() {
	// connect to redis and add queue
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
	}
	rcon.Cmd("SADD", "resque:queues", QUEUE)
	rcon.Close()

	// start web app
	http.HandleFunc(API_PATH, apiHandler)
	http.ListenAndServe(PORT, nil)
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		Text string `json:"text"`
	}

	type Job struct {
		Class string `json:"class"`
		Args map[string]string `json:"args"`
	}

	body := make([]byte, r.ContentLength)

	// accept only POST
	if r.Method == "POST" {
		// make connection to redis
		rcon, err := redis.Dial("tcp", REDIS)
		if err != nil {
			fmt.Println("error:", err)
		}

		// read and parse request body content
		r.Body.Read(body)
		data, err := url.ParseQuery(string(body))
		if err != nil {
			fmt.Println("error:", err)
		}

		// create job
		job := Job{
			Class: CLASS,
			Args: map[string]string {
			"request": strings.TrimPrefix(data.Get("text"), data.Get("trigger_word")),
			"user": data.Get("user_name"),
			"channel": data.Get("channel_name"),
			"timestamp": data.Get("timestamp"),
			},
		}
		jjob, err := json.Marshal(job)
		if err != nil {
			fmt.Println("error:", err)
		}
		// push job to resque
		queue := "resque:queue:" + QUEUE
		rcon.Cmd("RPUSH", queue, string(jjob))
		rcon.Close()

		// response to slack
		res := Response{
			Text: "@" + data.Get("user_name") + ": " + strings.TrimPrefix(data.Get("text"), "slackops: "),
		}
		jres, err := json.Marshal(res)
		if err != nil {
			fmt.Println("error:", err)
		}
		w.Header().Set("Content-type", "application/json")
		fmt.Fprintf(w, string(jres))
	}
}
<commit_msg>add config parameter<commit_after>/*
Tiny web server to handle slack outgoing webhook and push the data to resque
*/
package main

import (
	"fmt"
	"flag"
	"net/http"
	"net/url"
	"encoding/json"
	"strings"
	"github.com/fzzy/radix/redis"
)

// some default global variable
var (
	PORT string
	API_PATH string = "/api"
	REDIS string
	CLASS string
	QUEUE string
)

func main() {
	// config parameter
	p_port := flag.String("p", "8765", "listen port")
	p_redis := flag.String("r", "localhost:6379", "redis host")
	p_class := flag.String("c", "SlackOPS", "resque class")
	p_queue := flag.String("q", "slackops", "resque queue")
	flag.Parse()

	PORT = ":" + *p_port
	REDIS = *p_redis
	CLASS = *p_class
	QUEUE = *p_queue

	// connect to redis and add queue
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
	}
	rcon.Cmd("SADD", "resque:queues", QUEUE)
	rcon.Close()

	// start web app
	http.HandleFunc(API_PATH, apiHandler)
	http.ListenAndServe(PORT, nil)
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		Text string `json:"text"`
	}

	type Job struct {
		Class string `json:"class"`
		Args map[string]string `json:"args"`
	}

	body := make([]byte, r.ContentLength)

	// accept only POST
	if r.Method == "POST" {
		// make connection to redis
		rcon, err := redis.Dial("tcp", REDIS)
		if err != nil {
			fmt.Println("error:", err)
		}

		// read and parse request body content
		r.Body.Read(body)
		data, err := url.ParseQuery(string(body))
		if err != nil {
			fmt.Println("error:", err)
		}

		// create job
		job := Job{
			Class: CLASS,
			Args: map[string]string {
			"request": strings.TrimPrefix(data.Get("text"), data.Get("trigger_word")),
			"user": data.Get("user_name"),
			"channel": data.Get("channel_name"),
			"timestamp": data.Get("timestamp"),
			},
		}
		jjob, err := json.Marshal(job)
		if err != nil {
			fmt.Println("error:", err)
		}
		// push job to resque
		queue := "resque:queue:" + QUEUE
		rcon.Cmd("RPUSH", queue, string(jjob))
		rcon.Close()

		// response to slack
		res := Response{
			Text: "@" + data.Get("user_name") + ": " + strings.TrimPrefix(data.Get("text"), "slackops: "),
		}
		jres, err := json.Marshal(res)
		if err != nil {
			fmt.Println("error:", err)
		}
		w.Header().Set("Content-type", "application/json")
		fmt.Fprintf(w, string(jres))
	}
}
<|endoftext|>


<commit_before>package gitmedia

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

const Version = "0.2.0"

var (
	LargeSizeThreshold = 5 * 1024 * 1024
	TempDir            = filepath.Join(os.TempDir(), "git-media")
	LocalWorkingDir    string
	LocalGitDir        string
	LocalMediaDir      string
	LocalLogDir        string
	checkedTempDir     string
)

func TempFile(prefix string) (*os.File, error) {
	if checkedTempDir != TempDir {
		if err := os.MkdirAll(TempDir, 0774); err != nil {
			return nil, err
		}
		checkedTempDir = TempDir
	}

	return ioutil.TempFile(TempDir, prefix)
}

func ResetTempDir() error {
	checkedTempDir = ""
	return os.RemoveAll(TempDir)
}

func LocalMediaPath(sha string) (string, error) {
	path := filepath.Join(LocalMediaDir, sha[0:2], sha[2:4])
	if err := os.MkdirAll(path, 0744); err != nil {
		return "", fmt.Errorf("Error trying to create local media directory in '%s': %s", path, err)
	}

	return filepath.Join(path, sha), nil
}

func Environ() []string {
	osEnviron := os.Environ()
	env := make([]string, 4, len(osEnviron)+4)
	env[0] = fmt.Sprintf("LocalWorkingDir=%s", LocalWorkingDir)
	env[1] = fmt.Sprintf("LocalGitDir=%s", LocalGitDir)
	env[2] = fmt.Sprintf("LocalMediaDir=%s", LocalMediaDir)
	env[3] = fmt.Sprintf("TempDir=%s", TempDir)

	for _, e := range osEnviron {
		if !strings.Contains(e, "GIT_") {
			continue
		}
		env = append(env, e)
	}

	return env
}

func InRepo() bool {
	return LocalWorkingDir != ""
}

func init() {
	var err error
	LocalWorkingDir, LocalGitDir, err = resolveGitDir()
	if err == nil {
		LocalMediaDir = filepath.Join(LocalGitDir, "media")
		LocalLogDir = filepath.Join(LocalMediaDir, "logs")
		TempDir = filepath.Join(LocalMediaDir, "tmp")
		queueDir = setupQueueDir()

		if err := os.MkdirAll(TempDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err))
		}
	}
}

func resolveGitDir() (string, string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	return recursiveResolveGitDir(wd)
}

func recursiveResolveGitDir(dir string) (string, string, error) {
	if len(dir) == 1 && dir[0] == os.PathSeparator {
		return "", "", fmt.Errorf("Git repository not found")
	}

	if filepath.Base(dir) == gitExt {
		return filepath.Dir(dir), dir, nil
	}

	gitDir := filepath.Join(dir, gitExt)
	if info, err := os.Stat(gitDir); err == nil {
		if info.IsDir() {
			return dir, gitDir, nil
		} else {
			return processDotGitFile(gitDir)
		}
	}

	return recursiveResolveGitDir(filepath.Dir(dir))
}

func processDotGitFile(file string) (string, string, error) {
	f, err := os.Open(file)
	defer f.Close()

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

	data := make([]byte, 512)
	n, err := f.Read(data)
	if err != nil {
		return "", "", err
	}

	contents := string(data[0:n])
	wd, _ := os.Getwd()
	if strings.HasPrefix(contents, gitPtrPrefix) {
		dir := strings.TrimSpace(strings.Split(contents, gitPtrPrefix)[1])
		absDir, _ := filepath.Abs(dir)
		return wd, absDir, nil
	}

	return wd, "", nil
}

const (
	gitExt       = ".git"
	gitPtrPrefix = "gitdir: "
)
<commit_msg>ンンン ンンン ンン<commit_after>package gitmedia

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

const Version = "0.2.0"

var (
	LargeSizeThreshold = 5 * 1024 * 1024
	TempDir            = filepath.Join(os.TempDir(), "git-media")
	LocalWorkingDir    string
	LocalGitDir        string
	LocalMediaDir      string
	LocalLogDir        string
	checkedTempDir     string
)

func TempFile(prefix string) (*os.File, error) {
	if checkedTempDir != TempDir {
		if err := os.MkdirAll(TempDir, 0774); err != nil {
			return nil, err
		}
		checkedTempDir = TempDir
	}

	return ioutil.TempFile(TempDir, prefix)
}

func ResetTempDir() error {
	checkedTempDir = ""
	return os.RemoveAll(TempDir)
}

func LocalMediaPath(sha string) (string, error) {
	path := filepath.Join(LocalMediaDir, sha[0:2], sha[2:4])
	if err := os.MkdirAll(path, 0744); err != nil {
		return "", fmt.Errorf("Error trying to create local media directory in '%s': %s", path, err)
	}

	return filepath.Join(path, sha), nil
}

func Environ() []string {
	osEnviron := os.Environ()
	env := make([]string, 4, len(osEnviron)+4)
	env[0] = fmt.Sprintf("LocalWorkingDir=%s", LocalWorkingDir)
	env[1] = fmt.Sprintf("LocalGitDir=%s", LocalGitDir)
	env[2] = fmt.Sprintf("LocalMediaDir=%s", LocalMediaDir)
	env[3] = fmt.Sprintf("TempDir=%s", TempDir)

	for _, e := range osEnviron {
		if !strings.Contains(e, "GIT_") {
			continue
		}
		env = append(env, e)
	}

	return env
}

func InRepo() bool {
	return LocalWorkingDir != ""
}

func init() {
	var err error
	LocalWorkingDir, LocalGitDir, err = resolveGitDir()
	if err == nil {
		LocalMediaDir = filepath.Join(LocalGitDir, "media")
		LocalLogDir = filepath.Join(LocalMediaDir, "logs")
		TempDir = filepath.Join(LocalMediaDir, "tmp")
		queueDir = setupQueueDir()

		if err := os.MkdirAll(TempDir, 0744); err != nil {
			panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err))
		}
	}
}

func resolveGitDir() (string, string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	return recursiveResolveGitDir(wd)
}

func recursiveResolveGitDir(dir string) (string, string, error) {
	var cleanDir = filepath.Clean(dir)
	if cleanDir[len(cleanDir)-1] == os.PathSeparator {
		return "", "", fmt.Errorf("Git repository not found")
	}

	if filepath.Base(dir) == gitExt {
		return filepath.Dir(dir), dir, nil
	}

	gitDir := filepath.Join(dir, gitExt)
	if info, err := os.Stat(gitDir); err == nil {
		if info.IsDir() {
			return dir, gitDir, nil
		} else {
			return processDotGitFile(gitDir)
		}
	}

	return recursiveResolveGitDir(filepath.Dir(dir))
}

func processDotGitFile(file string) (string, string, error) {
	f, err := os.Open(file)
	defer f.Close()

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

	data := make([]byte, 512)
	n, err := f.Read(data)
	if err != nil {
		return "", "", err
	}

	contents := string(data[0:n])
	wd, _ := os.Getwd()
	if strings.HasPrefix(contents, gitPtrPrefix) {
		dir := strings.TrimSpace(strings.Split(contents, gitPtrPrefix)[1])
		absDir, _ := filepath.Abs(dir)
		return wd, absDir, nil
	}

	return wd, "", nil
}

const (
	gitExt       = ".git"
	gitPtrPrefix = "gitdir: "
)
<|endoftext|>


<commit_before>// Copyright 2014 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.

// +build linux darwin

package glutil

import (
	"encoding/binary"
	"image"
	"sync"

	"golang.org/x/mobile/f32"
	"golang.org/x/mobile/geom"
	"golang.org/x/mobile/gl"
)

var glimage struct {
	sync.Once
	quadXY        gl.Buffer
	quadUV        gl.Buffer
	program       gl.Program
	pos           gl.Attrib
	mvp           gl.Uniform
	uvp           gl.Uniform
	inUV          gl.Attrib
	textureSample gl.Uniform
}

func glInit() {
	var err error
	glimage.program, err = CreateProgram(vertexShader, fragmentShader)
	if err != nil {
		panic(err)
	}

	glimage.quadXY = gl.CreateBuffer()
	glimage.quadUV = gl.CreateBuffer()

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
	gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
	gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)

	glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
	glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
	glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
	glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
	glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
}

// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the embedded *image.RGBA can be uploaded as a
// texture and drawn as a 2D quad.
//
// The number of active Images must fit in the system's OpenGL texture
// limit. The typical use of an Image is as a texture atlas.
type Image struct {
	*image.RGBA

	Texture   gl.Texture
	texWidth  int
	texHeight int
}

// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func NewImage(w, h int) *Image {
	dx := roundToPower2(w)
	dy := roundToPower2(h)

	// TODO(crawshaw): Using VertexAttribPointer we can pass texture
	// data with a stride, which would let us use the exact number of
	// pixels on the host instead of the rounded up power 2 size.
	m := image.NewRGBA(image.Rect(0, 0, dx, dy))

	glimage.Do(glInit)

	img := &Image{
		RGBA:      m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
		Texture:   gl.CreateTexture(),
		texWidth:  dx,
		texHeight: dy,
	}
	// TODO(crawshaw): We don't have the context on a finalizer. Find a way.
	// runtime.SetFinalizer(img, func(img *Image) { gl.DeleteTexture(img.Texture) })
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexImage2D(gl.TEXTURE_2D, 0, dx, dy, gl.RGBA, gl.UNSIGNED_BYTE, nil)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	return img
}

func roundToPower2(x int) int {
	x2 := 1
	for x2 < x {
		x2 *= 2
	}
	return x2
}

// Upload copies the host image data to the GL device.
func (img *Image) Upload() {
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
}

// Draw draws the srcBounds part of the image onto a parallelogram, defined by
// three of its corners, in the current GL framebuffer.
func (img *Image) Draw(topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
	// TODO(crawshaw): Adjust viewport for the top bar on android?
	gl.UseProgram(glimage.program)

	{
		// We are drawing a parallelogram PQRS, defined by three of its
		// corners, onto the entire GL framebuffer ABCD. The two quads may
		// actually be equal, but in the general case, PQRS can be smaller,
		// and PQRS is not necessarily axis-aligned.
		//
		//	A +---------------+ B
		//	  |  P +-----+ Q  |
		//	  |    |     |    |
		//	  |  S +-----+ R  |
		//	D +---------------+ C
		//
		// There are two co-ordinate spaces: geom space and framebuffer space.
		// In geom space, the ABCD rectangle is:
		//
		//	(0, 0)           (geom.Width, 0)
		//	(0, geom.Height) (geom.Width, geom.Height)
		//
		// and the PQRS quad is:
		//
		//	(topLeft.X,    topLeft.Y)    (topRight.X, topRight.Y)
		//	(bottomLeft.X, bottomLeft.Y) (implicit,   implicit)
		//
		// In framebuffer space, the ABCD rectangle is:
		//
		//	(-1, +1) (+1, +1)
		//	(-1, -1) (+1, -1)
		//
		// First of all, convert from geom space to framebuffer space. For
		// later convenience, we divide everything by 2 here: px2 is half of
		// the P.X co-ordinate (in framebuffer space).
		px2 := -0.5 + float32(topLeft.X/geom.Width)
		py2 := +0.5 - float32(topLeft.Y/geom.Height)
		qx2 := -0.5 + float32(topRight.X/geom.Width)
		qy2 := +0.5 - float32(topRight.Y/geom.Height)
		sx2 := -0.5 + float32(bottomLeft.X/geom.Width)
		sy2 := +0.5 - float32(bottomLeft.Y/geom.Height)
		// Next, solve for the affine transformation matrix
		//	    [ a00 a01 a02 ]
		//	a = [ a10 a11 a12 ]
		//	    [   0   0   1 ]
		// that maps A to P:
		//	a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
		// and likewise maps B to Q and D to S. Solving those three constraints
		// implies that C maps to R, since affine transformations keep parallel
		// lines parallel. This gives 6 equations in 6 unknowns:
		//	-a00 + a01 + a02 = 2*px2
		//	-a10 + a11 + a12 = 2*py2
		//	+a00 + a01 + a02 = 2*qx2
		//	+a10 + a11 + a12 = 2*qy2
		//	-a00 - a01 + a02 = 2*sx2
		//	-a10 - a11 + a12 = 2*sy2
		// which gives:
		//	a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
		// and similarly for the other elements of a.
		glimage.mvp.WriteAffine(&f32.Affine{{
			qx2 - px2,
			px2 - sx2,
			qx2 + sx2,
		}, {
			qy2 - py2,
			py2 - sy2,
			qy2 + sy2,
		}})
	}

	{
		// Mapping texture co-ordinates is similar, except that in texture
		// space, the ABCD rectangle is:
		//
		//	(0,0) (1,0)
		//	(0,1) (1,1)
		//
		// and the PQRS quad is always axis-aligned. First of all, convert
		// from pixel space to texture space.
		w := float32(img.texWidth)
		h := float32(img.texHeight)
		px := float32(srcBounds.Min.X-img.Rect.Min.X) / w
		py := float32(srcBounds.Min.Y-img.Rect.Min.Y) / h
		qx := float32(srcBounds.Max.X-img.Rect.Min.X) / w
		sy := float32(srcBounds.Max.Y-img.Rect.Min.Y) / h
		// Due to axis alignment, qy = py and sx = px.
		//
		// The simultaneous equations are:
		//	  0 +   0 + a02 = px
		//	  0 +   0 + a12 = py
		//	a00 +   0 + a02 = qx
		//	a10 +   0 + a12 = qy = py
		//	  0 + a01 + a02 = sx = px
		//	  0 + a11 + a12 = sy
		glimage.uvp.WriteAffine(&f32.Affine{{
			qx - px,
			0,
			px,
		}, {
			0,
			sy - py,
			py,
		}})
	}

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.Uniform1i(glimage.textureSample, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
	gl.EnableVertexAttribArray(glimage.pos)
	gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
	gl.EnableVertexAttribArray(glimage.inUV)
	gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)

	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)

	gl.DisableVertexAttribArray(glimage.pos)
	gl.DisableVertexAttribArray(glimage.inUV)
}

var quadXYCoords = f32.Bytes(binary.LittleEndian,
	-1, +1, // top left
	+1, +1, // top right
	-1, -1, // bottom left
	+1, -1, // bottom right
)

var quadUVCoords = f32.Bytes(binary.LittleEndian,
	0, 0, // top left
	1, 0, // top right
	0, 1, // bottom left
	1, 1, // bottom right
)

const vertexShader = `#version 100
uniform mat3 mvp;
uniform mat3 uvp;
attribute vec3 pos;
attribute vec2 inUV;
varying vec2 UV;
void main() {
	vec3 p = pos;
	p.z = 1.0;
	gl_Position = vec4(mvp * p, 1);
	UV = (uvp * vec3(inUV, 1)).xy;
}
`

const fragmentShader = `#version 100
precision mediump float;
varying vec2 UV;
uniform sampler2D textureSample;
void main(){
	gl_FragColor = texture2D(textureSample, UV);
}
`
<commit_msg>gl/glutil: manage GL textures across start/stop<commit_after>// Copyright 2014 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.

// +build linux darwin

package glutil

import (
	"encoding/binary"
	"fmt"
	"image"
	"runtime"
	"sync"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/f32"
	"golang.org/x/mobile/geom"
	"golang.org/x/mobile/gl"
)

var glimage struct {
	quadXY        gl.Buffer
	quadUV        gl.Buffer
	program       gl.Program
	pos           gl.Attrib
	mvp           gl.Uniform
	uvp           gl.Uniform
	inUV          gl.Attrib
	textureSample gl.Uniform
}

func init() {
	app.Register(app.Callbacks{
		Start: start,
		Stop:  stop,
	})
}

func start() {
	var err error
	glimage.program, err = CreateProgram(vertexShader, fragmentShader)
	if err != nil {
		panic(err)
	}

	glimage.quadXY = gl.CreateBuffer()
	glimage.quadUV = gl.CreateBuffer()

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
	gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
	gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)

	glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
	glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
	glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
	glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
	glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")

	texmap.Lock()
	defer texmap.Unlock()
	for key, tex := range texmap.texs {
		texmap.init(key)
		tex.needsUpload = true
	}
}

func stop() {
	gl.DeleteProgram(glimage.program)
	gl.DeleteBuffer(glimage.quadXY)
	gl.DeleteBuffer(glimage.quadUV)

	texmap.Lock()
	for _, t := range texmap.texs {
		if t.gltex.Value != 0 {
			gl.DeleteTexture(t.gltex)
		}
		t.gltex = gl.Texture{}
	}
	texmap.Unlock()
}

type texture struct {
	gltex       gl.Texture
	width       int
	height      int
	needsUpload bool
}

var texmap = &texmapCache{
	texs: make(map[texmapKey]*texture),
	next: 1, // avoid using 0 to aid debugging
}

type texmapKey int

type texmapCache struct {
	sync.Mutex
	texs map[texmapKey]*texture
	next texmapKey

	// TODO(crawshaw): This is a workaround for having nowhere better to clean up deleted textures.
	// Better: app.UI(func() { gl.DeleteTexture(t) } in texmap.delete
	// Best: Redesign the gl package to do away with this painful notion of a UI thread.
	toDelete []gl.Texture
}

func (tm *texmapCache) create(dx, dy int) *texmapKey {
	tm.Lock()
	defer tm.Unlock()
	key := tm.next
	tm.next++
	tm.texs[key] = &texture{
		width:  dx,
		height: dy,
	}
	tm.init(key)
	return &key
}

// init creates an underlying GL texture for a key.
// Must be called with a valid GL context.
// Must hold tm.Mutex before calling.
func (tm *texmapCache) init(key texmapKey) {
	tex := tm.texs[key]
	if tex.gltex.Value != 0 {
		panic(fmt.Sprintf("attempting to init key (%v) with valid texture", key))
	}
	tex.gltex = gl.CreateTexture()

	gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
	gl.TexImage2D(gl.TEXTURE_2D, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, nil)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	for _, t := range tm.toDelete {
		gl.DeleteTexture(t)
	}
	tm.toDelete = nil
}

func (tm *texmapCache) delete(key texmapKey) {
	tm.Lock()
	defer tm.Unlock()
	tex := tm.texs[key]
	delete(tm.texs, key)
	if tex == nil {
		return
	}
	tm.toDelete = append(tm.toDelete, tex.gltex)
}

func (tm *texmapCache) get(key texmapKey) *texture {
	tm.Lock()
	defer tm.Unlock()
	return tm.texs[key]
}

// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the embedded *image.RGBA can be uploaded as a
// texture and drawn as a 2D quad.
//
// The number of active Images must fit in the system's OpenGL texture
// limit. The typical use of an Image is as a texture atlas.
type Image struct {
	*image.RGBA
	key *texmapKey
}

// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func NewImage(w, h int) *Image {
	dx := roundToPower2(w)
	dy := roundToPower2(h)

	// TODO(crawshaw): Using VertexAttribPointer we can pass texture
	// data with a stride, which would let us use the exact number of
	// pixels on the host instead of the rounded up power 2 size.
	m := image.NewRGBA(image.Rect(0, 0, dx, dy))

	img := &Image{
		RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
		key:  texmap.create(dx, dy),
	}
	runtime.SetFinalizer(img.key, func(key *texmapKey) {
		texmap.delete(*key)
	})
	return img
}

func roundToPower2(x int) int {
	x2 := 1
	for x2 < x {
		x2 *= 2
	}
	return x2
}

// Upload copies the host image data to the GL device.
func (img *Image) Upload() {
	tex := texmap.get(*img.key)
	gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
	gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
}

// Delete invalidates the Image and removes any underlying data structures.
// The Image cannot be used after being deleted.
func (img *Image) Delete() {
	texmap.delete(*img.key)
}

// Draw draws the srcBounds part of the image onto a parallelogram, defined by
// three of its corners, in the current GL framebuffer.
func (img *Image) Draw(topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
	// TODO(crawshaw): Adjust viewport for the top bar on android?
	gl.UseProgram(glimage.program)
	tex := texmap.get(*img.key)
	if tex.needsUpload {
		img.Upload()
		tex.needsUpload = false
	}

	{
		// We are drawing a parallelogram PQRS, defined by three of its
		// corners, onto the entire GL framebuffer ABCD. The two quads may
		// actually be equal, but in the general case, PQRS can be smaller,
		// and PQRS is not necessarily axis-aligned.
		//
		//	A +---------------+ B
		//	  |  P +-----+ Q  |
		//	  |    |     |    |
		//	  |  S +-----+ R  |
		//	D +---------------+ C
		//
		// There are two co-ordinate spaces: geom space and framebuffer space.
		// In geom space, the ABCD rectangle is:
		//
		//	(0, 0)           (geom.Width, 0)
		//	(0, geom.Height) (geom.Width, geom.Height)
		//
		// and the PQRS quad is:
		//
		//	(topLeft.X,    topLeft.Y)    (topRight.X, topRight.Y)
		//	(bottomLeft.X, bottomLeft.Y) (implicit,   implicit)
		//
		// In framebuffer space, the ABCD rectangle is:
		//
		//	(-1, +1) (+1, +1)
		//	(-1, -1) (+1, -1)
		//
		// First of all, convert from geom space to framebuffer space. For
		// later convenience, we divide everything by 2 here: px2 is half of
		// the P.X co-ordinate (in framebuffer space).
		px2 := -0.5 + float32(topLeft.X/geom.Width)
		py2 := +0.5 - float32(topLeft.Y/geom.Height)
		qx2 := -0.5 + float32(topRight.X/geom.Width)
		qy2 := +0.5 - float32(topRight.Y/geom.Height)
		sx2 := -0.5 + float32(bottomLeft.X/geom.Width)
		sy2 := +0.5 - float32(bottomLeft.Y/geom.Height)
		// Next, solve for the affine transformation matrix
		//	    [ a00 a01 a02 ]
		//	a = [ a10 a11 a12 ]
		//	    [   0   0   1 ]
		// that maps A to P:
		//	a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
		// and likewise maps B to Q and D to S. Solving those three constraints
		// implies that C maps to R, since affine transformations keep parallel
		// lines parallel. This gives 6 equations in 6 unknowns:
		//	-a00 + a01 + a02 = 2*px2
		//	-a10 + a11 + a12 = 2*py2
		//	+a00 + a01 + a02 = 2*qx2
		//	+a10 + a11 + a12 = 2*qy2
		//	-a00 - a01 + a02 = 2*sx2
		//	-a10 - a11 + a12 = 2*sy2
		// which gives:
		//	a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
		// and similarly for the other elements of a.
		glimage.mvp.WriteAffine(&f32.Affine{{
			qx2 - px2,
			px2 - sx2,
			qx2 + sx2,
		}, {
			qy2 - py2,
			py2 - sy2,
			qy2 + sy2,
		}})
	}

	{
		// Mapping texture co-ordinates is similar, except that in texture
		// space, the ABCD rectangle is:
		//
		//	(0,0) (1,0)
		//	(0,1) (1,1)
		//
		// and the PQRS quad is always axis-aligned. First of all, convert
		// from pixel space to texture space.
		w := float32(tex.width)
		h := float32(tex.height)
		px := float32(srcBounds.Min.X-img.Rect.Min.X) / w
		py := float32(srcBounds.Min.Y-img.Rect.Min.Y) / h
		qx := float32(srcBounds.Max.X-img.Rect.Min.X) / w
		sy := float32(srcBounds.Max.Y-img.Rect.Min.Y) / h
		// Due to axis alignment, qy = py and sx = px.
		//
		// The simultaneous equations are:
		//	  0 +   0 + a02 = px
		//	  0 +   0 + a12 = py
		//	a00 +   0 + a02 = qx
		//	a10 +   0 + a12 = qy = py
		//	  0 + a01 + a02 = sx = px
		//	  0 + a11 + a12 = sy
		glimage.uvp.WriteAffine(&f32.Affine{{
			qx - px,
			0,
			px,
		}, {
			0,
			sy - py,
			py,
		}})
	}

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, tex.gltex)
	gl.Uniform1i(glimage.textureSample, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
	gl.EnableVertexAttribArray(glimage.pos)
	gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
	gl.EnableVertexAttribArray(glimage.inUV)
	gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)

	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)

	gl.DisableVertexAttribArray(glimage.pos)
	gl.DisableVertexAttribArray(glimage.inUV)
}

var quadXYCoords = f32.Bytes(binary.LittleEndian,
	-1, +1, // top left
	+1, +1, // top right
	-1, -1, // bottom left
	+1, -1, // bottom right
)

var quadUVCoords = f32.Bytes(binary.LittleEndian,
	0, 0, // top left
	1, 0, // top right
	0, 1, // bottom left
	1, 1, // bottom right
)

const vertexShader = `#version 100
uniform mat3 mvp;
uniform mat3 uvp;
attribute vec3 pos;
attribute vec2 inUV;
varying vec2 UV;
void main() {
	vec3 p = pos;
	p.z = 1.0;
	gl_Position = vec4(mvp * p, 1);
	UV = (uvp * vec3(inUV, 1)).xy;
}
`

const fragmentShader = `#version 100
precision mediump float;
varying vec2 UV;
uniform sampler2D textureSample;
void main(){
	gl_FragColor = texture2D(textureSample, UV);
}
`
<|endoftext|>