<commit_before>package local

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/creativesoftwarefdn/weaviate/models"
	"github.com/creativesoftwarefdn/weaviate/schema/kind"
	log "github.com/sirupsen/logrus"
)

type LocalSchemaConfig struct {
	StateDir *string `json:"state_dir"`
}

type LocalSchemaManager struct {
	// The directory where the state will be stored
	stateDir string

	// A file handler to the state file.
	stateFile   *os.File
	schemaState localSchemaState
}

// The state that will be serialized to/from disk.
// TODO: refactor to database/schema_manager, so that it can be re-used for distributed version.
type localSchemaState struct {
	actionSchema *models.SemanticSchema `json:"action"`
	thingSchema  *models.SemanticSchema `json:"thing"`
}

func (l *localSchemaState) SchemaFor(k kind.Kind) *models.SemanticSchema {
	switch k {
	case kind.THING_KIND:
		return l.thingSchema
	case kind.ACTION_KIND:
		return l.actionSchema
	default:
		log.Fatal("Passed wrong neither thing nor kind, but %v", k)
		return nil
	}
}

func New(stateDirName string) (*LocalSchemaManager, error) {
	manager := &LocalSchemaManager{
		stateDir:    stateDirName,
		schemaState: localSchemaState{},
	}

	err := manager.load()
	if err != nil {
		return nil, err
	}

	return manager, nil
}

// Load the state from a file, or if the files do not exist yet, initialize an empty schema.
func (l *LocalSchemaManager) load() error {
	// Verify that the directory exists and is indeed a directory.
	dirStat, err := os.Stat(l.stateDir)
	if err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Directory '%v' does not exist", l.stateDir)
		} else {
			return fmt.Errorf("Could not look up stats of '%v'", l.stateDir)
		}
	} else {
		if !dirStat.IsDir() {
			return fmt.Errorf("The path '%v' is not a directory!", l.stateDir)
		}
	}

	// Check if the schema files exist.
	stateFileStat, err := os.Stat(l.statePath())
	var stateFileExists bool = true
	if err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Could not look up stats of '%v'", l.statePath())
		} else {
			stateFileExists = false
		}
	} else {
		if stateFileStat.IsDir() {
			return fmt.Errorf("The path '%v' is a directory, not a file!", l.statePath())
		}
	}

	// Open the file. If it's created, only allow the current user to access it.
	l.stateFile, err = os.OpenFile(l.statePath(), os.O_RDWR|os.O_CREATE, 0600)
	if err != nil {
		return fmt.Errorf("Could not open state file '%v'", l.statePath())
	}

	// Fill in empty ontologies if there is no schema file.
	if !stateFileExists {
		l.schemaState.actionSchema = &models.SemanticSchema{
			Type: "action",
		}

		l.schemaState.thingSchema = &models.SemanticSchema{
			Type: "thing",
		}

		// And flush the schema to disk.
		err := l.saveToDisk()
		if err != nil {
			log.Fatal("Could not save empty schema to disk")
		}
		log.Infof("Initialized empty schema")
	} else {
		// Load the state from disk.
		stateBytes, _ := ioutil.ReadAll(l.stateFile)
		var state localSchemaState
		err := json.Unmarshal(stateBytes, &state)
		if err != nil {
			return fmt.Errorf("Could not parse the schema state file '%v'", l.statePath())
		} else {
			l.schemaState = state
		}
	}
	return nil
}

func (l *LocalSchemaManager) statePath() string {
	return l.stateDir + "/state.json"
}

// Save the schema to the local disk.
func (l *LocalSchemaManager) saveToDisk() error {
	// TODO not 100% robust against failures.
	// we don't check IO errors yet

	stateBytes, err := json.Marshal(l.schemaState)
	if err != nil {
		return err
	}

	err = l.stateFile.Truncate(0)
	if err != nil {
		return err
	}

	_, err = l.stateFile.Seek(0, 0)
	if err != nil {
		return err
	}

	l.stateFile.Write(stateBytes)

	return nil
}
<commit_msg>gh-486: make field members public, so that json (de)seralization works<commit_after>package local

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/creativesoftwarefdn/weaviate/models"
	"github.com/creativesoftwarefdn/weaviate/schema/kind"
	log "github.com/sirupsen/logrus"
)

type LocalSchemaConfig struct {
	StateDir *string `json:"state_dir"`
}

type LocalSchemaManager struct {
	// The directory where the state will be stored
	stateDir string

	// A file handler to the state file.
	stateFile   *os.File
	schemaState localSchemaState
}

// The state that will be serialized to/from disk.
// TODO: refactor to database/schema_manager, so that it can be re-used for distributed version.
type localSchemaState struct {
	ActionSchema *models.SemanticSchema `json:"action"`
	ThingSchema  *models.SemanticSchema `json:"thing"`
}

func (l *localSchemaState) SchemaFor(k kind.Kind) *models.SemanticSchema {
	switch k {
	case kind.THING_KIND:
		return l.ThingSchema
	case kind.ACTION_KIND:
		return l.ActionSchema
	default:
		log.Fatal("Passed wrong neither thing nor kind, but %v", k)
		return nil
	}
}

func New(stateDirName string) (*LocalSchemaManager, error) {
	manager := &LocalSchemaManager{
		stateDir:    stateDirName,
		schemaState: localSchemaState{},
	}

	err := manager.load()
	if err != nil {
		return nil, err
	}

	return manager, nil
}

// Load the state from a file, or if the files do not exist yet, initialize an empty schema.
func (l *LocalSchemaManager) load() error {
	// Verify that the directory exists and is indeed a directory.
	dirStat, err := os.Stat(l.stateDir)
	if err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Directory '%v' does not exist", l.stateDir)
		} else {
			return fmt.Errorf("Could not look up stats of '%v'", l.stateDir)
		}
	} else {
		if !dirStat.IsDir() {
			return fmt.Errorf("The path '%v' is not a directory!", l.stateDir)
		}
	}

	// Check if the schema files exist.
	stateFileStat, err := os.Stat(l.statePath())
	var stateFileExists bool = true
	if err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Could not look up stats of '%v'", l.statePath())
		} else {
			stateFileExists = false
		}
	} else {
		if stateFileStat.IsDir() {
			return fmt.Errorf("The path '%v' is a directory, not a file!", l.statePath())
		}
	}

	// Open the file. If it's created, only allow the current user to access it.
	l.stateFile, err = os.OpenFile(l.statePath(), os.O_RDWR|os.O_CREATE, 0600)
	if err != nil {
		return fmt.Errorf("Could not open state file '%v'", l.statePath())
	}

	// Fill in empty ontologies if there is no schema file.
	if !stateFileExists {
		l.schemaState.ActionSchema = &models.SemanticSchema{
			Type: "action",
		}

		l.schemaState.ThingSchema = &models.SemanticSchema{
			Type: "thing",
		}

		// And flush the schema to disk.
		err := l.saveToDisk()
		if err != nil {
			log.Fatal("Could not save empty schema to disk")
		}
		log.Infof("Initialized empty schema")
	} else {
		// Load the state from disk.
		stateBytes, _ := ioutil.ReadAll(l.stateFile)
		var state localSchemaState
		err := json.Unmarshal(stateBytes, &state)
		if err != nil {
			return fmt.Errorf("Could not parse the schema state file '%v'", l.statePath())
		} else {
			l.schemaState = state
		}
	}
	return nil
}

func (l *LocalSchemaManager) statePath() string {
	return l.stateDir + "/state.json"
}

// Save the schema to the local disk.
func (l *LocalSchemaManager) saveToDisk() error {
	log.Info("Updating local schema on disk")
	// TODO not 100% robust against failures.
	// we don't check IO errors yet

	stateBytes, err := json.Marshal(l.schemaState)
	if err != nil {
		return err
	}

	err = l.stateFile.Truncate(0)
	if err != nil {
		return err
	}

	_, err = l.stateFile.Seek(0, 0)
	if err != nil {
		return err
	}

	l.stateFile.Write(stateBytes)
	l.stateFile.Sync()

	return nil
}
<|endoftext|>


<commit_before>package ez

import (
	"reflect"
	"testing"
)

func TestIn(t *testing.T) {
	args := []interface{}{true, 42, "foo"}
	cin := in{tuple{args}, "unit_test.go", 11}
	if in := In(args...); !reflect.DeepEqual(cin, in) {
		t.Errorf("In(%v)\nwant (%v)\nhave (%v)", args, cin, in)
	}
}

func TestOut(t *testing.T) {
	args := []interface{}{true, 42, "foo"}
	cout := out{tuple{args}, "unit_test.go", 19, nil}
	if out := Out(args...); !reflect.DeepEqual(cout, out) {
		t.Errorf("Out(%v)\nwant (%v)\nhave (%v)", args, cout, out)
	}
}
<commit_msg>Add test for Panic<commit_after>package ez

import (
	"errors"
	"reflect"
	"testing"
)

func TestIn(t *testing.T) {
	args := []interface{}{true, 42, "foo"}
	cin := in{tuple{args}, "unit_test.go", 12}
	if in := In(args...); !reflect.DeepEqual(cin, in) {
		t.Errorf("In(%v)\nwant (%v)\nhave (%v)", args, cin, in)
	}
}

func TestOut(t *testing.T) {
	args := []interface{}{true, 42, "foo"}
	cout := out{tuple{args}, "unit_test.go", 20, nil}
	if out := Out(args...); !reflect.DeepEqual(cout, out) {
		t.Errorf("Out(%v)\nwant (%v)\nhave (%v)", args, cout, out)
	}
}

func TestPanic(t *testing.T) {
	p := errors.New("bar")
	cout := out{tuple{}, "unit_test.go", 28, p}
	if out := Panic(p); !reflect.DeepEqual(cout, out) {
		t.Errorf("Panic(%v)\nwant (%v)\nhave (%v)", p, cout, out)
	}
}
<|endoftext|>


<commit_before>package user

import (
	jwt "github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
	"time"

	"github.com/eirka/eirka-libs/db"
	e "github.com/eirka/eirka-libs/errors"
)

const (
	// claim key for jwt token
	user_id_claim = "user_id"
)

// user struct
type User struct {
	Id              uint
	Name            string
	IsAuthenticated bool
	hash            []byte
}

// create a user struct
func DefaultUser() User {
	return User{
		Id:              1,
		IsAuthenticated: false,
	}
}

// check user struct validity
func (u *User) IsValid() bool {

	// this isnt a real user id
	if u.Id == 0 {
		return false
	}

	// the anon account can never be authenticated
	if u.Id == 1 && u.IsAuthenticated {
		return false
	}

	// a user can never be unauthenticated
	if u.Id != 1 && !u.IsAuthenticated {
		return false
	}

	return true
}

// sets the user id
func (u *User) SetId(uid uint) {
	u.Id = uid
	return
}

// sets authenticated
func (u *User) SetAuthenticated() {
	u.IsAuthenticated = true
	return
}

func (u *User) FromName(name string) (err error) {

	// password length cant be 0
	if len(name) == 0 {
		return e.ErrUserNotValid
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	// get hashed password from database
	err = dbase.QueryRow("select user_id, user_password from users where user_name = ?", name).Scan(&u.Id, &u.hash)
	if err != nil {
		return
	}

	u.SetAuthenticated()

	if !u.IsValid() {
		return e.ErrUserNotValid
	}

	return

}

// check for duplicate name before registering
func CheckDuplicate(name string) (check bool) {

	// password length cant be 0
	if len(name) == 0 {
		return false
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return false
	}

	err = dbase.QueryRow("select count(*) from users where user_name = ?", name).Scan(&check)
	if err != nil {
		return false
	}

	return

}

// Creates a JWT token with our claims
func (u *User) CreateToken() (newtoken string, err error) {

	if !u.IsValid() {
		err = e.ErrUserNotValid
		return
	}

	// error if theres no secret set
	if Secret == "" {
		err = e.ErrNoSecret
		return
	}

	// Create the token
	token := jwt.New(jwt.SigningMethodHS256)

	// Set our claims
	token.Claims["iss"] = "pram"
	token.Claims["iat"] = time.Now().Unix()
	token.Claims["exp"] = time.Now().Add(time.Hour * 24 * 90).Unix()
	token.Claims[user_id_claim] = u.Id

	// Sign and get the complete encoded token as a string
	newtoken, err = token.SignedString([]byte(Secret))
	if err != nil {
		return
	}

	return

}

// get the user info from id
func (u *User) IsAuthorized(ib uint) bool {

	var err error

	if !u.IsValid() {
		return false
	}

	// check for invalid stuff
	if ib == 0 {
		return false
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return false
	}

	// holds our role
	var role uint

	// get data from users table
	err = dbase.QueryRow(`SELECT COALESCE((SELECT MAX(role_id) FROM user_ib_role_map WHERE user_ib_role_map.user_id = users.user_id AND ib_id = ?),user_role_map.role_id) as role
    FROM users
    INNER JOIN user_role_map ON (user_role_map.user_id = users.user_id)
    WHERE users.user_id = ?`, ib, u.Id).Scan(&role)
	if err != nil {
		return false
	}

	switch role {
	case 3:
		return true
	case 4:
		return true
	default:
		return false
	}

	return false

}

// compare password to
func (u *User) ComparePassword(password string) bool {

	// password length cant be 0
	if len(password) == 0 {
		return false
	}

	// if the hash wasnt populated
	if len(u.hash) == 0 {
		return false
	}

	// compare the stored hash with the provided password
	err := bcrypt.CompareHashAndPassword(u.hash, []byte(password))

	return err == nil

}

// hash a given password
func HashPassword(password string) (hash []byte, err error) {

	// password length cant be 0
	if len(password) == 0 {
		err = e.ErrInvalidPassword
		return
	}

	return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}
<commit_msg>move user functions to lib<commit_after>package user

import (
	jwt "github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
	"time"

	"github.com/eirka/eirka-libs/db"
	e "github.com/eirka/eirka-libs/errors"
)

const (
	// claim key for jwt token
	user_id_claim = "user_id"
)

// user struct
type User struct {
	Id              uint
	Name            string
	IsAuthenticated bool
	hash            []byte
}

// create a user struct
func DefaultUser() User {
	return User{
		Id:              1,
		IsAuthenticated: false,
	}
}

// check user struct validity
func (u *User) IsValid() bool {

	// this isnt a real user id
	if u.Id == 0 {
		return false
	}

	// the anon account can never be authenticated
	if u.Id == 1 && u.IsAuthenticated {
		return false
	}

	// a user can never be unauthenticated
	if u.Id != 1 && !u.IsAuthenticated {
		return false
	}

	return true
}

// sets the user id
func (u *User) SetId(uid uint) {
	u.Id = uid
	return
}

// sets authenticated
func (u *User) SetAuthenticated() {
	u.IsAuthenticated = true
	return
}

func (u *User) FromName(name string) (err error) {

	// password length cant be 0
	if len(name) == 0 {
		return e.ErrUserNotValid
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	// get hashed password from database
	err = dbase.QueryRow("select user_id, user_password from users where user_name = ?", name).Scan(&u.Id, &u.hash)
	if err != nil {
		return
	}

	u.SetAuthenticated()

	if !u.IsValid() {
		return e.ErrUserNotValid
	}

	return

}

// check for duplicate name before registering
func CheckDuplicate(name string) (check bool) {

	// password length cant be 0
	if len(name) == 0 {
		return true
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return true
	}

	// this will return true if there is a user
	err = dbase.QueryRow("select count(*) from users where user_name = ?", name).Scan(&check)
	if err != nil {
		return true
	}

	return

}

// Creates a JWT token with our claims
func (u *User) CreateToken() (newtoken string, err error) {

	if !u.IsValid() {
		err = e.ErrUserNotValid
		return
	}

	// error if theres no secret set
	if Secret == "" {
		err = e.ErrNoSecret
		return
	}

	// Create the token
	token := jwt.New(jwt.SigningMethodHS256)

	// Set our claims
	token.Claims["iss"] = "pram"
	token.Claims["iat"] = time.Now().Unix()
	token.Claims["exp"] = time.Now().Add(time.Hour * 24 * 90).Unix()
	token.Claims[user_id_claim] = u.Id

	// Sign and get the complete encoded token as a string
	newtoken, err = token.SignedString([]byte(Secret))
	if err != nil {
		return
	}

	return

}

// get the user info from id
func (u *User) IsAuthorized(ib uint) bool {

	var err error

	if !u.IsValid() {
		return false
	}

	// check for invalid stuff
	if ib == 0 {
		return false
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return false
	}

	// holds our role
	var role uint

	// get data from users table
	err = dbase.QueryRow(`SELECT COALESCE((SELECT MAX(role_id) FROM user_ib_role_map WHERE user_ib_role_map.user_id = users.user_id AND ib_id = ?),user_role_map.role_id) as role
    FROM users
    INNER JOIN user_role_map ON (user_role_map.user_id = users.user_id)
    WHERE users.user_id = ?`, ib, u.Id).Scan(&role)
	if err != nil {
		return false
	}

	switch role {
	case 3:
		return true
	case 4:
		return true
	default:
		return false
	}

	return false

}

// compare password to
func (u *User) ComparePassword(password string) bool {

	// password length cant be 0
	if len(password) == 0 {
		return false
	}

	// if the hash wasnt populated
	if len(u.hash) == 0 {
		return false
	}

	// compare the stored hash with the provided password
	err := bcrypt.CompareHashAndPassword(u.hash, []byte(password))

	return err == nil

}

// hash a given password
func HashPassword(password string) (hash []byte, err error) {

	// password length cant be 0
	if len(password) == 0 {
		err = e.ErrInvalidPassword
		return
	}

	return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}
<|endoftext|>


<commit_before>package slack

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strconv"
	"sync"
	"time"

	"golang.org/x/net/websocket"
)

type MessageEvent Message

type SlackWS struct {
	conn      *websocket.Conn
	messageId int
	mutex     sync.Mutex
	pings     map[int]time.Time
	Slack
}

// AckMessage is used for messages received in reply to other messages
type AckMessage struct {
	ReplyTo   int    `json:"reply_to"`
	Timestamp string `json:"ts"`
	Text      string `json:"text"`
	SlackWSResponse
}

type SlackWSResponse struct {
	Ok    bool          `json:"ok"`
	Error *SlackWSError `json:"error"`
}

type SlackWSError struct {
	Code int
	Msg  string
}

type SlackEvent struct {
	Type uint64
	Data interface{}
}

type JSONTimeString string

// String converts the unix timestamp into a string
func (t JSONTimeString) String() string {
	if t == "" {
		return ""
	}
	floatN, err := strconv.ParseFloat(string(t), 64)
	if err != nil {
		log.Panicln(err)
		return ""
	}
	timeStr := int64(floatN)
	tm := time.Unix(int64(timeStr), 0)
	return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
}

func (s SlackWSError) Error() string {
	return s.Msg
}

var portMapping = map[string]string{"ws": "80", "wss": "443"}

func fixUrlPort(orig string) (string, error) {
	urlObj, err := url.ParseRequestURI(orig)
	if err != nil {
		return "", err
	}
	_, _, err = net.SplitHostPort(urlObj.Host)
	if err != nil {
		return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
	}
	return orig, nil
}

func (api *Slack) StartRTM(protocol, origin string) (*SlackWS, error) {
	response := &infoResponseFull{}
	err := parseResponse("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, response.Error
	}
	api.info = response.Info
	// websocket.Dial does not accept url without the port (yet)
	// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
	// but slack returns the address with no port, so we have to fix it
	api.info.Url, err = fixUrlPort(api.info.Url)
	if err != nil {
		return nil, err
	}
	api.config.protocol, api.config.origin = protocol, origin
	wsApi := &SlackWS{Slack: *api}
	wsApi.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
	if err != nil {
		return nil, err
	}
	wsApi.pings = make(map[int]time.Time)
	return wsApi, nil
}

func (api *SlackWS) Ping() error {
	api.mutex.Lock()
	defer api.mutex.Unlock()
	api.messageId++
	msg := &Ping{Id: api.messageId, Type: "ping"}
	if err := websocket.JSON.Send(api.conn, msg); err != nil {
		return err
	}
	// TODO: What happens if we already have this id?
	api.pings[api.messageId] = time.Now()
	return nil
}

func (api *SlackWS) Keepalive(interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := api.Ping(); err != nil {
				log.Fatal(err)
			}
		}
	}
}

func (api *SlackWS) SendMessage(msg *OutgoingMessage) error {
	if msg == nil {
		return fmt.Errorf("Can't send a nil message")
	}

	if err := websocket.JSON.Send(api.conn, *msg); err != nil {
		return err
	}
	return nil
}

func (api *SlackWS) HandleIncomingEvents(ch chan SlackEvent) {
	for {
		event := json.RawMessage{}
		if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF {
			//log.Println("Derpi derp, should we destroy conn and start over?")
			//if err = api.StartRTM(); err != nil {
			//	log.Fatal(err)
			//}
			// should we reconnect here?
			if !api.conn.IsClientConn() {
				api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
				if err != nil {
					log.Panic(err)
				}
			}
			// XXX: check for timeout and implement exponential backoff
		} else if err != nil {
			log.Panic(err)
		}
		if len(event) == 0 {
			log.Println("Event Empty. WTF?")
		} else {
			if api.debug {
				log.Println(string(event[:]))
			}
			api.handleEvent(ch, event)
		}
		time.Sleep(time.Millisecond * 500)
	}
}

func (api *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
	em := Event{}
	err := json.Unmarshal(event, &em)
	if err != nil {
		log.Fatal(err)
	}
	switch em.Type {
	case "":
		// try ok
		ack := AckMessage{}
		if err = json.Unmarshal(event, &ack); err != nil {
			log.Fatal(err)
		}

		if ack.Ok {
			// TODO: Send the ack back (is this useful?)
			//ch <- SlackEvent{Type: EventAck, Data: ack}
			log.Printf("Received an ok for: %d", ack.ReplyTo)
			return
		}

		// Send the error to the user
		ch <- SlackEvent{Data: ack.Error}
	case "hello":
		ch <- SlackEvent{Data: HelloEvent{}}
	case "pong":
		pong := Pong{}
		if err = json.Unmarshal(event, &pong); err != nil {
			log.Fatal(err)
		}
		api.mutex.Lock()
		latency := time.Since(api.pings[pong.ReplyTo])
		api.mutex.Unlock()
		ch <- SlackEvent{Data: LatencyReport{Value: latency}}
	default:
		callEvent(em.Type, ch, event)
	}
}

func callEvent(eventType string, ch chan SlackEvent, event json.RawMessage) {
	eventMapping := map[string]interface{}{
		"message":         &MessageEvent{},
		"presence_change": &PresenceChangeEvent{},
		"user_typing":     &UserTypingEvent{},

		"channel_marked":          &ChannelMarkedEvent{},
		"channel_created":         &ChannelCreatedEvent{},
		"channel_joined":          &ChannelJoinedEvent{},
		"channel_left":            &ChannelLeftEvent{},
		"channel_deleted":         &ChannelDeletedEvent{},
		"channel_rename":          &ChannelRenameEvent{},
		"channel_archive":         &ChannelArchiveEvent{},
		"channel_unarchive":       &ChannelUnarchiveEvent{},
		"channel_history_changed": &ChannelHistoryChangedEvent{},

		"im_created":         &IMCreatedEvent{},
		"im_open":            &IMOpenEvent{},
		"im_close":           &IMCloseEvent{},
		"im_marked":          &IMMarkedEvent{},
		"im_history_changed": &IMHistoryChangedEvent{},

		"group_marked":          &GroupMarkedEvent{},
		"group_open":            &GroupOpenEvent{},
		"group_joined":          &GroupJoinedEvent{},
		"group_left":            &GroupLeftEvent{},
		"group_close":           &GroupCloseEvent{},
		"group_rename":          &GroupRenameEvent{},
		"group_archive":         &GroupArchiveEvent{},
		"group_unarchive":       &GroupUnarchiveEvent{},
		"group_history_changed": &GroupHistoryChangedEvent{},

		"file_created":         &FileCreatedEvent{},
		"file_shared":          &FileSharedEvent{},
		"file_unshared":        &FileUnsharedEvent{},
		"file_public":          &FilePublicEvent{},
		"file_private":         &FilePrivateEvent{},
		"file_change":          &FileChangeEvent{},
		"file_deleted":         &FileDeletedEvent{},
		"file_comment_added":   &FileCommentAddedEvent{},
		"file_comment_edited":  &FileCommentEditedEvent{},
		"file_comment_deleted": &FileCommentDeletedEvent{},

		"star_added":   &StarAddedEvent{},
		"star_removed": &StarRemovedEvent{},

		"pref_change": &PrefChangeEvent{},

		"team_join":              &TeamJoinEvent{},
		"team_rename":            &TeamRenameEvent{},
		"team_pref_change":       &TeamPrefChangeEvent{},
		"team_domain_change":     &TeamDomainChangeEvent{},
		"team_migration_started": &TeamMigrationStartedEvent{},

		"manual_presence_change": &ManualPresenceChangeEvent{},

		"user_change": &UserChangeEvent{},

		"emoji_changed": &EmojiChangedEvent{},

		"commands_changed": &CommandsChangedEvent{},

		"email_domain_changed": &EmailDomainChangedEvent{},

		"bot_added":   &BotAddedEvent{},
		"bot_changed": &BotChangedEvent{},

		"accounts_changed": &AccountsChangedEvent{},
	}

	msg := eventMapping[eventType]
	if msg == nil {
		log.Printf("XXX: Not implemented yet: %s -> %v", eventType, event)
	}
	if err := json.Unmarshal(event, &msg); err != nil {
		log.Fatal(err)
	}
	ch <- SlackEvent{Data: msg}
}
<commit_msg>more resilient websocket junk<commit_after>package slack

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strconv"
	"sync"
	"time"

	"golang.org/x/net/websocket"
)

type MessageEvent Message

type SlackWS struct {
	conn      *websocket.Conn
	messageId int
	mutex     sync.Mutex
	pings     map[int]time.Time
	Slack
}

// AckMessage is used for messages received in reply to other messages
type AckMessage struct {
	ReplyTo   int    `json:"reply_to"`
	Timestamp string `json:"ts"`
	Text      string `json:"text"`
	SlackWSResponse
}

type SlackWSResponse struct {
	Ok    bool          `json:"ok"`
	Error *SlackWSError `json:"error"`
}

type SlackWSError struct {
	Code int
	Msg  string
}

type SlackEvent struct {
	Type uint64
	Data interface{}
}

type JSONTimeString string

// String converts the unix timestamp into a string
func (t JSONTimeString) String() string {
	if t == "" {
		return ""
	}
	floatN, err := strconv.ParseFloat(string(t), 64)
	if err != nil {
		log.Panicln(err)
		return ""
	}
	timeStr := int64(floatN)
	tm := time.Unix(int64(timeStr), 0)
	return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
}

func (s SlackWSError) Error() string {
	return s.Msg
}

var portMapping = map[string]string{"ws": "80", "wss": "443"}

func fixUrlPort(orig string) (string, error) {
	urlObj, err := url.ParseRequestURI(orig)
	if err != nil {
		return "", err
	}
	_, _, err = net.SplitHostPort(urlObj.Host)
	if err != nil {
		return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
	}
	return orig, nil
}

func (api *Slack) StartRTM(protocol, origin string) (*SlackWS, error) {
	response := &infoResponseFull{}
	err := parseResponse("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, response.Error
	}
	api.info = response.Info
	// websocket.Dial does not accept url without the port (yet)
	// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
	// but slack returns the address with no port, so we have to fix it
	api.info.Url, err = fixUrlPort(api.info.Url)
	if err != nil {
		return nil, err
	}
	api.config.protocol, api.config.origin = protocol, origin
	wsApi := &SlackWS{Slack: *api}
	wsApi.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
	if err != nil {
		return nil, err
	}
	wsApi.pings = make(map[int]time.Time)
	return wsApi, nil
}

func (api *SlackWS) Ping() error {
	api.mutex.Lock()
	defer api.mutex.Unlock()
	api.messageId++
	msg := &Ping{Id: api.messageId, Type: "ping"}
	if err := websocket.JSON.Send(api.conn, msg); err != nil {
		return err
	}
	// TODO: What happens if we already have this id?
	api.pings[api.messageId] = time.Now()
	return nil
}

func (api *SlackWS) Keepalive(interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := api.Ping(); err != nil {
				log.Fatal(err)
			}
		}
	}
}

func (api *SlackWS) SendMessage(msg *OutgoingMessage) error {
	if msg == nil {
		return fmt.Errorf("Can't send a nil message")
	}

	if err := websocket.JSON.Send(api.conn, *msg); err != nil {
		return err
	}
	return nil
}

func (api *SlackWS) HandleIncomingEvents(ch chan SlackEvent) {
	eofCount := 0
	log.Println("entering loop")
	defer close(ch)
	for {
		event := json.RawMessage{}
		if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF {
			log.Println("got EOF!")
			//log.Println("Derpi derp, should we destroy conn and start over?")
			//if err = api.StartRTM(); err != nil {
			//	log.Fatal(err)
			//}
			// should we reconnect here?
			if !api.conn.IsClientConn() || eofCount > 10 {
				log.Println("reconnecting")
				eofCount = 0
				api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
				if err != nil {
					log.Panic(err)
				}
			} else {
				log.Println("but client was connected...", api.conn, eofCount)
				eofCount++
				log.Println("eofcount", eofCount)
			}
			goto SLEEP
			// XXX: check for timeout and implement exponential backoff
		} else if err != nil {
			log.Panic(err)
		}
		if len(event) == 0 {
			log.Println("Event Empty. WTF?")
		} else {
			if api.debug {
				log.Println("event", string(event[:]))
			}
			eofCount = 0
			api.handleEvent(ch, event)
		}
	SLEEP:
		time.Sleep(time.Millisecond * 500)
	}
}

func (api *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
	em := Event{}
	err := json.Unmarshal(event, &em)
	if err != nil {
		log.Fatal("couldn't parse", string(event), err)
	}
	switch em.Type {
	case "":
		// try ok
		ack := AckMessage{}
		if err = json.Unmarshal(event, &ack); err != nil {
			log.Fatal("couldn't parse", string(event), err)
		}

		if ack.Ok {
			// TODO: Send the ack back (is this useful?)
			//ch <- SlackEvent{Type: EventAck, Data: ack}
			log.Printf("Received an ok for: %d", ack.ReplyTo)
			return
		}

		// Send the error to the user
		ch <- SlackEvent{Data: ack.Error}
	case "hello":
		ch <- SlackEvent{Data: HelloEvent{}}
	case "pong":
		pong := Pong{}
		if err = json.Unmarshal(event, &pong); err != nil {
			log.Fatal("couldn't parse", string(event), err)
		}
		api.mutex.Lock()
		latency := time.Since(api.pings[pong.ReplyTo])
		api.mutex.Unlock()
		ch <- SlackEvent{Data: LatencyReport{Value: latency}}
	default:
		callEvent(em.Type, ch, event)
	}
}

func callEvent(eventType string, ch chan SlackEvent, event json.RawMessage) {
	eventMapping := map[string]interface{}{
		"message":         &MessageEvent{},
		"presence_change": &PresenceChangeEvent{},
		"user_typing":     &UserTypingEvent{},

		"channel_marked":          &ChannelMarkedEvent{},
		"channel_created":         &ChannelCreatedEvent{},
		"channel_joined":          &ChannelJoinedEvent{},
		"channel_left":            &ChannelLeftEvent{},
		"channel_deleted":         &ChannelDeletedEvent{},
		"channel_rename":          &ChannelRenameEvent{},
		"channel_archive":         &ChannelArchiveEvent{},
		"channel_unarchive":       &ChannelUnarchiveEvent{},
		"channel_history_changed": &ChannelHistoryChangedEvent{},

		"im_created":         &IMCreatedEvent{},
		"im_open":            &IMOpenEvent{},
		"im_close":           &IMCloseEvent{},
		"im_marked":          &IMMarkedEvent{},
		"im_history_changed": &IMHistoryChangedEvent{},

		"group_marked":          &GroupMarkedEvent{},
		"group_open":            &GroupOpenEvent{},
		"group_joined":          &GroupJoinedEvent{},
		"group_left":            &GroupLeftEvent{},
		"group_close":           &GroupCloseEvent{},
		"group_rename":          &GroupRenameEvent{},
		"group_archive":         &GroupArchiveEvent{},
		"group_unarchive":       &GroupUnarchiveEvent{},
		"group_history_changed": &GroupHistoryChangedEvent{},

		"file_created":         &FileCreatedEvent{},
		"file_shared":          &FileSharedEvent{},
		"file_unshared":        &FileUnsharedEvent{},
		"file_public":          &FilePublicEvent{},
		"file_private":         &FilePrivateEvent{},
		"file_change":          &FileChangeEvent{},
		"file_deleted":         &FileDeletedEvent{},
		"file_comment_added":   &FileCommentAddedEvent{},
		"file_comment_edited":  &FileCommentEditedEvent{},
		"file_comment_deleted": &FileCommentDeletedEvent{},

		"star_added":   &StarAddedEvent{},
		"star_removed": &StarRemovedEvent{},

		"pref_change": &PrefChangeEvent{},

		"team_join":              &TeamJoinEvent{},
		"team_rename":            &TeamRenameEvent{},
		"team_pref_change":       &TeamPrefChangeEvent{},
		"team_domain_change":     &TeamDomainChangeEvent{},
		"team_migration_started": &TeamMigrationStartedEvent{},

		"manual_presence_change": &ManualPresenceChangeEvent{},

		"user_change": &UserChangeEvent{},

		"emoji_changed": &EmojiChangedEvent{},

		"commands_changed": &CommandsChangedEvent{},

		"email_domain_changed": &EmailDomainChangedEvent{},

		"bot_added":   &BotAddedEvent{},
		"bot_changed": &BotChangedEvent{},

		"accounts_changed": &AccountsChangedEvent{},
	}

	msg := eventMapping[eventType]
	if msg == nil {
		log.Printf("XXX: Not implemented yet: %s -> %v", eventType, event)
	}
	if err := json.Unmarshal(event, &msg); err != nil {
		log.Fatal(err)
	}
	ch <- SlackEvent{Data: msg}
}
<|endoftext|>


<commit_before>package util

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"path"
	"sync"
)

var InvalidItem = errors.New("invalid item")

// Access broker for a key=>value store on disk with reference counting. All
// methods are safe to call from multiple goroutines.
type Filestore struct {
	sync.Mutex
	directory string
	items     map[string]int
}

// Determine the name of the index file on disk.
func (f *Filestore) indexFilename() string {
	return path.Join(f.directory, "index.json")
}

// Update the item map on disk.
func (f *Filestore) update() error {
	if w, err := os.OpenFile(f.indexFilename(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err == nil {
		if err := json.NewEncoder(w).Encode(f.items); err == nil {
			return w.Close()
		} else {
			return err
		}
	} else {
		return err
	}
}

// Create a new filestore. An attempt will be made to load the index from disk.
func NewFilestore(directory string) (*Filestore, error) {
	f := &Filestore{
		directory: directory,
		items:     make(map[string]int),
	}
	if r, err := os.Open(f.indexFilename()); err == nil {
		if err := json.NewDecoder(r).Decode(f.items); err != nil {
			return nil, err
		}
		if err := r.Close(); err != nil {
			return nil, err
		}
	} else if !os.IsNotExist(err) {
		return nil, err
	}
	return f, nil
}

// Obtain a writer for creating a new item.
func (f *Filestore) New(id string) (io.WriteCloser, error) {
	f.Lock()
	defer f.Unlock()
	// TODO
	return nil, nil
}

// Obtain a reader for retrieving an exising item.
func (f *Filestore) Get(id string) (io.ReadCloser, error) {
	f.Lock()
	defer f.Unlock()
	if _, ok := f.items[id]; ok {
		return os.Open(path.Join(f.directory, id))
	} else {
		return nil, InvalidItem
	}
}

// Increase the reference count of the specified item.
func (f *Filestore) Add(id string) error {
	f.Lock()
	defer f.Unlock()
	if _, ok := f.items[id]; ok {
		f.items[id]++
		return f.update()
	} else {
		return InvalidItem
	}
}

// Decrease the reference count of the specified item. It will be deleted from
// disk when the reference count reaches zero.
func (f *Filestore) Release(id string) error {
	f.Lock()
	defer f.Unlock()
	if _, ok := f.items[id]; ok {
		f.items[id]--
		// TODO
		return f.update()
	} else {
		return InvalidItem
	}
}
<commit_msg>Finished initial implementation of Filestore.<commit_after>package util

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"path"
	"sync"
)

var InvalidItem = errors.New("invalid item")

// Access broker for a key=>value store on disk with reference counting. All
// methods are safe to call from multiple goroutines.
type Filestore struct {
	sync.Mutex
	directory string
	items     map[string]int
}

// Determine the name of the index file on disk.
func (f *Filestore) indexFilename() string {
	return path.Join(f.directory, "index.json")
}

// Update the item map on disk.
func (f *Filestore) update() error {
	if w, err := os.OpenFile(f.indexFilename(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err == nil {
		if err := json.NewEncoder(w).Encode(f.items); err == nil {
			return w.Close()
		} else {
			return err
		}
	} else {
		return err
	}
}

// Create a new filestore. An attempt will be made to load the index from disk.
func NewFilestore(directory string) (*Filestore, error) {
	f := &Filestore{
		directory: directory,
		items:     make(map[string]int),
	}
	if r, err := os.Open(f.indexFilename()); err == nil {
		if err := json.NewDecoder(r).Decode(f.items); err != nil {
			return nil, err
		}
		if err := r.Close(); err != nil {
			return nil, err
		}
	} else if !os.IsNotExist(err) {
		return nil, err
	}
	return f, nil
}

// Obtain a writer for creating a new item.
func (f *Filestore) New(id string) (io.WriteCloser, error) {
	f.Lock()
	defer f.Unlock()
	if w, err := os.OpenFile(path.Join(f.directory, id), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err == nil {
		return w, nil
	} else {
		return nil, err
	}
}

// Obtain a reader for retrieving an exising item.
func (f *Filestore) Get(id string) (io.ReadCloser, error) {
	f.Lock()
	defer f.Unlock()
	if _, ok := f.items[id]; ok {
		return os.Open(path.Join(f.directory, id))
	} else {
		return nil, InvalidItem
	}
}

// Increase the reference count of the specified item.
func (f *Filestore) Add(id string) error {
	f.Lock()
	defer f.Unlock()
	if _, ok := f.items[id]; ok {
		f.items[id]++
	} else {
		f.items[id] = 1
	}
	return f.update()
}

// Decrease the reference count of the specified item. It will be deleted from
// disk when the reference count reaches zero.
func (f *Filestore) Release(id string) error {
	f.Lock()
	defer f.Unlock()
	if n, ok := f.items[id]; ok {
		if n == 1 {
			delete(f.items, id)
			if err := os.Remove(path.Join(f.directory, id)); err != nil {
				return err
			}
		} else {
			f.items[id]--
		}
		return f.update()
	} else {
		return InvalidItem
	}
}
<|endoftext|>


<commit_before>package slack

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strconv"
	"sync"
	"time"

	"golang.org/x/net/websocket"
)

type MessageEvent Message

type SlackWS struct {
	conn      *websocket.Conn
	messageId int
	mutex     sync.Mutex
	pings     map[int]time.Time
	Slack
}

// AckMessage is used for messages received in reply to other messages
type AckMessage struct {
	ReplyTo   int    `json:"reply_to"`
	Timestamp string `json:"ts"`
	Text      string `json:"text"`
	SlackWSResponse
}

type SlackWSResponse struct {
	Ok    bool          `json:"ok"`
	Error *SlackWSError `json:"error"`
}

type SlackWSError struct {
	Code int
	Msg  string
}

type SlackEvent struct {
	Type uint64
	Data interface{}
}

type JSONTimeString string

// String converts the unix timestamp into a string
func (t JSONTimeString) String() string {
	if t == "" {
		return ""
	}
	floatN, err := strconv.ParseFloat(string(t), 64)
	if err != nil {
		log.Panicln(err)
		return ""
	}
	timeStr := int64(floatN)
	tm := time.Unix(int64(timeStr), 0)
	return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
}

func (s SlackWSError) Error() string {
	return s.Msg
}

var portMapping = map[string]string{"ws": "80", "wss": "443"}

func fixUrlPort(orig string) (string, error) {
	urlObj, err := url.ParseRequestURI(orig)
	if err != nil {
		return "", err
	}
	_, _, err = net.SplitHostPort(urlObj.Host)
	if err != nil {
		return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
	}
	return orig, nil
}

func (api *Slack) StartRTM(protocol, origin string) (*SlackWS, error) {
	response := &infoResponseFull{}
	err := parseResponse("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, response.Error
	}
	api.info = response.Info
	// websocket.Dial does not accept url without the port (yet)
	// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
	// but slack returns the address with no port, so we have to fix it
	api.info.Url, err = fixUrlPort(api.info.Url)
	if err != nil {
		return nil, err
	}
	api.config.protocol, api.config.origin = protocol, origin
	wsApi := &SlackWS{Slack: *api}
	wsApi.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
	if err != nil {
		return nil, err
	}
	wsApi.pings = make(map[int]time.Time)
	return wsApi, nil
}

func (api *SlackWS) Ping() error {
	api.mutex.Lock()
	defer api.mutex.Unlock()
	api.messageId++
	msg := &Ping{Id: api.messageId, Type: "ping"}
	if err := websocket.JSON.Send(api.conn, msg); err != nil {
		return err
	}
	// TODO: What happens if we already have this id?
	api.pings[api.messageId] = time.Now()
	return nil
}

func (api *SlackWS) Keepalive(interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := api.Ping(); err != nil {
				log.Fatal(err)
			}
		}
	}
}

func (api *SlackWS) SendMessage(msg *OutgoingMessage) error {
	if msg == nil {
		return fmt.Errorf("Can't send a nil message")
	}

	if err := websocket.JSON.Send(api.conn, *msg); err != nil {
		return err
	}
	return nil
}

func (api *SlackWS) HandleIncomingEvents(ch chan SlackEvent) {
	for {
		event := json.RawMessage{}
		if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF {
			//log.Println("Derpi derp, should we destroy conn and start over?")
			//if err = api.StartRTM(); err != nil {
			//	log.Fatal(err)
			//}
			// should we reconnect here?
			if !api.conn.IsClientConn() {
				api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
				if err != nil {
					log.Panic(err)
				}
			}
			// XXX: check for timeout and implement exponential backoff
		} else if err != nil {
			log.Panic(err)
		}
		if len(event) == 0 {
			if api.debug {
				log.Println("Event Empty. WTF?")
			}
		} else {
			if api.debug {
				log.Println(string(event[:]))
			}
			api.handleEvent(ch, event)
		}
		time.Sleep(time.Millisecond * 500)
	}
}

func (api *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
	em := Event{}
	err := json.Unmarshal(event, &em)
	if err != nil {
		log.Fatal(err)
	}
	switch em.Type {
	case "":
		// try ok
		ack := AckMessage{}
		if err = json.Unmarshal(event, &ack); err != nil {
			log.Fatal(err)
		}

		if ack.Ok {
			// TODO: Send the ack back (is this useful?)
			//ch <- SlackEvent{Type: EventAck, Data: ack}
			log.Printf("Received an ok for: %d", ack.ReplyTo)
			return
		}

		// Send the error to the user
		ch <- SlackEvent{Data: ack.Error}
	case "hello":
		ch <- SlackEvent{Data: HelloEvent{}}
	case "pong":
		pong := Pong{}
		if err = json.Unmarshal(event, &pong); err != nil {
			log.Fatal(err)
		}
		api.mutex.Lock()
		latency := time.Since(api.pings[pong.ReplyTo])
		api.mutex.Unlock()
		ch <- SlackEvent{Data: LatencyReport{Value: latency}}
	default:
		callEvent(em.Type, ch, event)
	}
}

func callEvent(eventType string, ch chan SlackEvent, event json.RawMessage) {
	eventMapping := map[string]interface{}{
		"message":         &MessageEvent{},
		"presence_change": &PresenceChangeEvent{},
		"user_typing":     &UserTypingEvent{},

		"channel_marked":          &ChannelMarkedEvent{},
		"channel_created":         &ChannelCreatedEvent{},
		"channel_joined":          &ChannelJoinedEvent{},
		"channel_left":            &ChannelLeftEvent{},
		"channel_deleted":         &ChannelDeletedEvent{},
		"channel_rename":          &ChannelRenameEvent{},
		"channel_archive":         &ChannelArchiveEvent{},
		"channel_unarchive":       &ChannelUnarchiveEvent{},
		"channel_history_changed": &ChannelHistoryChangedEvent{},

		"im_created":         &IMCreatedEvent{},
		"im_open":            &IMOpenEvent{},
		"im_close":           &IMCloseEvent{},
		"im_marked":          &IMMarkedEvent{},
		"im_history_changed": &IMHistoryChangedEvent{},

		"group_marked":          &GroupMarkedEvent{},
		"group_open":            &GroupOpenEvent{},
		"group_joined":          &GroupJoinedEvent{},
		"group_left":            &GroupLeftEvent{},
		"group_close":           &GroupCloseEvent{},
		"group_rename":          &GroupRenameEvent{},
		"group_archive":         &GroupArchiveEvent{},
		"group_unarchive":       &GroupUnarchiveEvent{},
		"group_history_changed": &GroupHistoryChangedEvent{},

		"file_created":         &FileCreatedEvent{},
		"file_shared":          &FileSharedEvent{},
		"file_unshared":        &FileUnsharedEvent{},
		"file_public":          &FilePublicEvent{},
		"file_private":         &FilePrivateEvent{},
		"file_change":          &FileChangeEvent{},
		"file_deleted":         &FileDeletedEvent{},
		"file_comment_added":   &FileCommentAddedEvent{},
		"file_comment_edited":  &FileCommentEditedEvent{},
		"file_comment_deleted": &FileCommentDeletedEvent{},

		"star_added":   &StarAddedEvent{},
		"star_removed": &StarRemovedEvent{},

		"pref_change": &PrefChangeEvent{},

		"team_join":              &TeamJoinEvent{},
		"team_rename":            &TeamRenameEvent{},
		"team_pref_change":       &TeamPrefChangeEvent{},
		"team_domain_change":     &TeamDomainChangeEvent{},
		"team_migration_started": &TeamMigrationStartedEvent{},

		"manual_presence_change": &ManualPresenceChangeEvent{},

		"user_change": &UserChangeEvent{},

		"emoji_changed": &EmojiChangedEvent{},

		"commands_changed": &CommandsChangedEvent{},

		"email_domain_changed": &EmailDomainChangedEvent{},

		"bot_added":   &BotAddedEvent{},
		"bot_changed": &BotChangedEvent{},

		"accounts_changed": &AccountsChangedEvent{},
	}

	msg := eventMapping[eventType]
	if msg == nil {
		log.Printf("XXX: Not implemented yet: %s -> %v", eventType, event)
	}
	if err := json.Unmarshal(event, &msg); err != nil {
		log.Fatal(err)
	}
	ch <- SlackEvent{Data: msg}
}
<commit_msg>websocket: don't kill ourselves if we have an unhandleable message<commit_after>package slack

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strconv"
	"sync"
	"time"

	"golang.org/x/net/websocket"
)

type MessageEvent Message

type SlackWS struct {
	conn      *websocket.Conn
	messageId int
	mutex     sync.Mutex
	pings     map[int]time.Time
	Slack
}

// AckMessage is used for messages received in reply to other messages
type AckMessage struct {
	ReplyTo   int    `json:"reply_to"`
	Timestamp string `json:"ts"`
	Text      string `json:"text"`
	SlackWSResponse
}

type SlackWSResponse struct {
	Ok    bool          `json:"ok"`
	Error *SlackWSError `json:"error"`
}

type SlackWSError struct {
	Code int
	Msg  string
}

type SlackEvent struct {
	Type uint64
	Data interface{}
}

type JSONTimeString string

// String converts the unix timestamp into a string
func (t JSONTimeString) String() string {
	if t == "" {
		return ""
	}
	floatN, err := strconv.ParseFloat(string(t), 64)
	if err != nil {
		log.Panicln(err)
		return ""
	}
	timeStr := int64(floatN)
	tm := time.Unix(int64(timeStr), 0)
	return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
}

func (s SlackWSError) Error() string {
	return s.Msg
}

var portMapping = map[string]string{"ws": "80", "wss": "443"}

func fixUrlPort(orig string) (string, error) {
	urlObj, err := url.ParseRequestURI(orig)
	if err != nil {
		return "", err
	}
	_, _, err = net.SplitHostPort(urlObj.Host)
	if err != nil {
		return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
	}
	return orig, nil
}

func (api *Slack) StartRTM(protocol, origin string) (*SlackWS, error) {
	response := &infoResponseFull{}
	err := parseResponse("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, response.Error
	}
	api.info = response.Info
	// websocket.Dial does not accept url without the port (yet)
	// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
	// but slack returns the address with no port, so we have to fix it
	api.info.Url, err = fixUrlPort(api.info.Url)
	if err != nil {
		return nil, err
	}
	api.config.protocol, api.config.origin = protocol, origin
	wsApi := &SlackWS{Slack: *api}
	wsApi.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
	if err != nil {
		return nil, err
	}
	wsApi.pings = make(map[int]time.Time)
	return wsApi, nil
}

func (api *SlackWS) Ping() error {
	api.mutex.Lock()
	defer api.mutex.Unlock()
	api.messageId++
	msg := &Ping{Id: api.messageId, Type: "ping"}
	if err := websocket.JSON.Send(api.conn, msg); err != nil {
		return err
	}
	// TODO: What happens if we already have this id?
	api.pings[api.messageId] = time.Now()
	return nil
}

func (api *SlackWS) Keepalive(interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if err := api.Ping(); err != nil {
				log.Fatal(err)
			}
		}
	}
}

func (api *SlackWS) SendMessage(msg *OutgoingMessage) error {
	if msg == nil {
		return fmt.Errorf("Can't send a nil message")
	}

	if err := websocket.JSON.Send(api.conn, *msg); err != nil {
		return err
	}
	return nil
}

func (api *SlackWS) HandleIncomingEvents(ch chan SlackEvent) {
	for {
		event := json.RawMessage{}
		if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF {
			//log.Println("Derpi derp, should we destroy conn and start over?")
			//if err = api.StartRTM(); err != nil {
			//	log.Fatal(err)
			//}
			// should we reconnect here?
			if !api.conn.IsClientConn() {
				api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
				if err != nil {
					log.Panic(err)
				}
			}
			// XXX: check for timeout and implement exponential backoff
		} else if err != nil {
			log.Panic(err)
		}
		if len(event) == 0 {
			if api.debug {
				log.Println("Event Empty. WTF?")
			}
		} else {
			if api.debug {
				log.Println(string(event[:]))
			}
			api.handleEvent(ch, event)
		}
		time.Sleep(time.Millisecond * 500)
	}
}

func (api *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
	em := Event{}
	err := json.Unmarshal(event, &em)
	if err != nil {
		log.Fatal(err)
	}
	switch em.Type {
	case "":
		// try ok
		ack := AckMessage{}
		if err = json.Unmarshal(event, &ack); err != nil {
			log.Fatal(err)
		}

		if ack.Ok {
			// TODO: Send the ack back (is this useful?)
			//ch <- SlackEvent{Type: EventAck, Data: ack}
			log.Printf("Received an ok for: %d", ack.ReplyTo)
			return
		}

		// Send the error to the user
		ch <- SlackEvent{Data: ack.Error}
	case "hello":
		ch <- SlackEvent{Data: HelloEvent{}}
	case "pong":
		pong := Pong{}
		if err = json.Unmarshal(event, &pong); err != nil {
			log.Fatal(err)
		}
		api.mutex.Lock()
		latency := time.Since(api.pings[pong.ReplyTo])
		api.mutex.Unlock()
		ch <- SlackEvent{Data: LatencyReport{Value: latency}}
	default:
		callEvent(em.Type, ch, event)
	}
}

func callEvent(eventType string, ch chan SlackEvent, event json.RawMessage) {
	eventMapping := map[string]interface{}{
		"message":         &MessageEvent{},
		"presence_change": &PresenceChangeEvent{},
		"user_typing":     &UserTypingEvent{},

		"channel_marked":          &ChannelMarkedEvent{},
		"channel_created":         &ChannelCreatedEvent{},
		"channel_joined":          &ChannelJoinedEvent{},
		"channel_left":            &ChannelLeftEvent{},
		"channel_deleted":         &ChannelDeletedEvent{},
		"channel_rename":          &ChannelRenameEvent{},
		"channel_archive":         &ChannelArchiveEvent{},
		"channel_unarchive":       &ChannelUnarchiveEvent{},
		"channel_history_changed": &ChannelHistoryChangedEvent{},

		"im_created":         &IMCreatedEvent{},
		"im_open":            &IMOpenEvent{},
		"im_close":           &IMCloseEvent{},
		"im_marked":          &IMMarkedEvent{},
		"im_history_changed": &IMHistoryChangedEvent{},

		"group_marked":          &GroupMarkedEvent{},
		"group_open":            &GroupOpenEvent{},
		"group_joined":          &GroupJoinedEvent{},
		"group_left":            &GroupLeftEvent{},
		"group_close":           &GroupCloseEvent{},
		"group_rename":          &GroupRenameEvent{},
		"group_archive":         &GroupArchiveEvent{},
		"group_unarchive":       &GroupUnarchiveEvent{},
		"group_history_changed": &GroupHistoryChangedEvent{},

		"file_created":         &FileCreatedEvent{},
		"file_shared":          &FileSharedEvent{},
		"file_unshared":        &FileUnsharedEvent{},
		"file_public":          &FilePublicEvent{},
		"file_private":         &FilePrivateEvent{},
		"file_change":          &FileChangeEvent{},
		"file_deleted":         &FileDeletedEvent{},
		"file_comment_added":   &FileCommentAddedEvent{},
		"file_comment_edited":  &FileCommentEditedEvent{},
		"file_comment_deleted": &FileCommentDeletedEvent{},

		"star_added":   &StarAddedEvent{},
		"star_removed": &StarRemovedEvent{},

		"pref_change": &PrefChangeEvent{},

		"team_join":              &TeamJoinEvent{},
		"team_rename":            &TeamRenameEvent{},
		"team_pref_change":       &TeamPrefChangeEvent{},
		"team_domain_change":     &TeamDomainChangeEvent{},
		"team_migration_started": &TeamMigrationStartedEvent{},

		"manual_presence_change": &ManualPresenceChangeEvent{},

		"user_change": &UserChangeEvent{},

		"emoji_changed": &EmojiChangedEvent{},

		"commands_changed": &CommandsChangedEvent{},

		"email_domain_changed": &EmailDomainChangedEvent{},

		"bot_added":   &BotAddedEvent{},
		"bot_changed": &BotChangedEvent{},

		"accounts_changed": &AccountsChangedEvent{},
	}

	msg := eventMapping[eventType]
	if msg == nil {
		log.Printf("XXX: Not implemented yet: %s -> %v", eventType, event)
	}
	if err := json.Unmarshal(event, &msg); err != nil {
		origErr := err
		msgBytes, err := event.MarshalJSON()
		if err != nil {
			log.Printf("unmarshalable %s w/ bad json: %s/%s", eventType, err, origErr)
		} else {
			log.Printf("unmarshalable %s: %s", eventType, string(msgBytes))
		}
		// no need to kill the process with a
		// log.Fatal or panic if we get a reply_to of
		// a string Timestamp rather than an int.
		return
	}
	ch <- SlackEvent{Data: msg}
}
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"encoding/pem"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"

	"golang.org/x/crypto/pkcs12"
)

// HTTPGet get 请求
func HTTPGet(uri string) ([]byte, error) {
	return HTTPGetContext(context.Background(), uri)
}

// HTTPGetContext get 请求
func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) {
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// HTTPPost post 请求
func HTTPPost(uri string, data string) ([]byte, error) {
	return HTTPPostContext(context.Background(), uri, []byte(data), nil)
}

// HTTPPostContext post 请求
func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[string]string) ([]byte, error) {
	body := bytes.NewBuffer(data)
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body)
	if err != nil {
		return nil, err
	}

	for key, value := range header {
		request.Header.Set(key, value)
	}

	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// PostJSON post json 数据请求
func PostJSON(uri string, obj interface{}) ([]byte, error) {
	jsonBuf := new(bytes.Buffer)
	enc := json.NewEncoder(jsonBuf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(obj)
	if err != nil {
		return nil, err
	}
	response, err := http.Post(uri, "application/json;charset=utf-8", jsonBuf)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// PostJSONWithRespContentType post json数据请求，且返回数据类型
func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, error) {
	jsonBuf := new(bytes.Buffer)
	enc := json.NewEncoder(jsonBuf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(obj)
	if err != nil {
		return nil, "", err
	}

	response, err := http.Post(uri, "application/json;charset=utf-8", jsonBuf)
	if err != nil {
		return nil, "", err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, "", fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	responseData, err := ioutil.ReadAll(response.Body)
	contentType := response.Header.Get("Content-Type")
	return responseData, contentType, err
}

// PostFile 上传文件
func PostFile(fieldName, filename, uri string) ([]byte, error) {
	fields := []MultipartFormField{
		{
			IsFile:    true,
			Fieldname: fieldName,
			Filename:  filename,
		},
	}
	return PostMultipartForm(fields, uri)
}

// MultipartFormField 保存文件或其他字段信息
type MultipartFormField struct {
	IsFile    bool
	Fieldname string
	Value     []byte
	Filename  string
}

// PostMultipartForm 上传文件或其他多个字段
func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte, err error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	for _, field := range fields {
		if field.IsFile {
			fileWriter, e := bodyWriter.CreateFormFile(field.Fieldname, field.Filename)
			if e != nil {
				err = fmt.Errorf("error writing to buffer , err=%v", e)
				return
			}

			fh, e := os.Open(field.Filename)
			if e != nil {
				err = fmt.Errorf("error opening file , err=%v", e)
				return
			}
			defer fh.Close()

			if _, err = io.Copy(fileWriter, fh); err != nil {
				return
			}
		} else {
			partWriter, e := bodyWriter.CreateFormField(field.Fieldname)
			if e != nil {
				err = e
				return
			}
			valueReader := bytes.NewReader(field.Value)
			if _, err = io.Copy(partWriter, valueReader); err != nil {
				return
			}
		}
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	resp, e := http.Post(uri, contentType, bodyBuf)
	if e != nil {
		err = e
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, err
	}
	respBody, err = ioutil.ReadAll(resp.Body)
	return
}

// PostXML perform a HTTP/POST request with XML body
func PostXML(uri string, obj interface{}) ([]byte, error) {
	xmlData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}

	body := bytes.NewBuffer(xmlData)
	response, err := http.Post(uri, "application/xml;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// httpWithTLS CA证书
func httpWithTLS(rootCa, key string) (*http.Client, error) {
	var client *http.Client
	certData, err := ioutil.ReadFile(rootCa)
	if err != nil {
		return nil, fmt.Errorf("unable to find cert path=%s, error=%v", rootCa, err)
	}
	cert := pkcs12ToPem(certData, key)
	config := &tls.Config{
		Certificates: []tls.Certificate{cert},
	}
	tr := &http.Transport{
		TLSClientConfig:    config,
		DisableCompression: true,
	}
	client = &http.Client{Transport: tr}
	return client, nil
}

// pkcs12ToPem 将Pkcs12转成Pem
func pkcs12ToPem(p12 []byte, password string) tls.Certificate {
	blocks, err := pkcs12.ToPEM(p12, password)
	defer func() {
		if x := recover(); x != nil {
			log.Print(x)
		}
	}()
	if err != nil {
		panic(err)
	}
	var pemData []byte
	for _, b := range blocks {
		pemData = append(pemData, pem.EncodeToMemory(b)...)
	}
	cert, err := tls.X509KeyPair(pemData, pemData)
	if err != nil {
		panic(err)
	}
	return cert
}

// PostXMLWithTLS perform a HTTP/POST request with XML body and TLS
func PostXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) {
	xmlData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}

	body := bytes.NewBuffer(xmlData)
	client, err := httpWithTLS(ca, key)
	if err != nil {
		return nil, err
	}
	response, err := client.Post(uri, "application/xml;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}
<commit_msg>fix: typo (#595)<commit_after>package util

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"encoding/pem"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"

	"golang.org/x/crypto/pkcs12"
)

// HTTPGet get 请求
func HTTPGet(uri string) ([]byte, error) {
	return HTTPGetContext(context.Background(), uri)
}

// HTTPGetContext get 请求
func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) {
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// HTTPPost post 请求
func HTTPPost(uri string, data string) ([]byte, error) {
	return HTTPPostContext(context.Background(), uri, []byte(data), nil)
}

// HTTPPostContext post 请求
func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[string]string) ([]byte, error) {
	body := bytes.NewBuffer(data)
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body)
	if err != nil {
		return nil, err
	}

	for key, value := range header {
		request.Header.Set(key, value)
	}

	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http post error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// PostJSON post json 数据请求
func PostJSON(uri string, obj interface{}) ([]byte, error) {
	jsonBuf := new(bytes.Buffer)
	enc := json.NewEncoder(jsonBuf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(obj)
	if err != nil {
		return nil, err
	}
	response, err := http.Post(uri, "application/json;charset=utf-8", jsonBuf)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// PostJSONWithRespContentType post json数据请求，且返回数据类型
func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, error) {
	jsonBuf := new(bytes.Buffer)
	enc := json.NewEncoder(jsonBuf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(obj)
	if err != nil {
		return nil, "", err
	}

	response, err := http.Post(uri, "application/json;charset=utf-8", jsonBuf)
	if err != nil {
		return nil, "", err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, "", fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	responseData, err := ioutil.ReadAll(response.Body)
	contentType := response.Header.Get("Content-Type")
	return responseData, contentType, err
}

// PostFile 上传文件
func PostFile(fieldName, filename, uri string) ([]byte, error) {
	fields := []MultipartFormField{
		{
			IsFile:    true,
			Fieldname: fieldName,
			Filename:  filename,
		},
	}
	return PostMultipartForm(fields, uri)
}

// MultipartFormField 保存文件或其他字段信息
type MultipartFormField struct {
	IsFile    bool
	Fieldname string
	Value     []byte
	Filename  string
}

// PostMultipartForm 上传文件或其他多个字段
func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte, err error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	for _, field := range fields {
		if field.IsFile {
			fileWriter, e := bodyWriter.CreateFormFile(field.Fieldname, field.Filename)
			if e != nil {
				err = fmt.Errorf("error writing to buffer , err=%v", e)
				return
			}

			fh, e := os.Open(field.Filename)
			if e != nil {
				err = fmt.Errorf("error opening file , err=%v", e)
				return
			}
			defer fh.Close()

			if _, err = io.Copy(fileWriter, fh); err != nil {
				return
			}
		} else {
			partWriter, e := bodyWriter.CreateFormField(field.Fieldname)
			if e != nil {
				err = e
				return
			}
			valueReader := bytes.NewReader(field.Value)
			if _, err = io.Copy(partWriter, valueReader); err != nil {
				return
			}
		}
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	resp, e := http.Post(uri, contentType, bodyBuf)
	if e != nil {
		err = e
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, err
	}
	respBody, err = ioutil.ReadAll(resp.Body)
	return
}

// PostXML perform a HTTP/POST request with XML body
func PostXML(uri string, obj interface{}) ([]byte, error) {
	xmlData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}

	body := bytes.NewBuffer(xmlData)
	response, err := http.Post(uri, "application/xml;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// httpWithTLS CA证书
func httpWithTLS(rootCa, key string) (*http.Client, error) {
	var client *http.Client
	certData, err := ioutil.ReadFile(rootCa)
	if err != nil {
		return nil, fmt.Errorf("unable to find cert path=%s, error=%v", rootCa, err)
	}
	cert := pkcs12ToPem(certData, key)
	config := &tls.Config{
		Certificates: []tls.Certificate{cert},
	}
	tr := &http.Transport{
		TLSClientConfig:    config,
		DisableCompression: true,
	}
	client = &http.Client{Transport: tr}
	return client, nil
}

// pkcs12ToPem 将Pkcs12转成Pem
func pkcs12ToPem(p12 []byte, password string) tls.Certificate {
	blocks, err := pkcs12.ToPEM(p12, password)
	defer func() {
		if x := recover(); x != nil {
			log.Print(x)
		}
	}()
	if err != nil {
		panic(err)
	}
	var pemData []byte
	for _, b := range blocks {
		pemData = append(pemData, pem.EncodeToMemory(b)...)
	}
	cert, err := tls.X509KeyPair(pemData, pemData)
	if err != nil {
		panic(err)
	}
	return cert
}

// PostXMLWithTLS perform a HTTP/POST request with XML body and TLS
func PostXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) {
	xmlData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}

	body := bytes.NewBuffer(xmlData)
	client, err := httpWithTLS(ca, key)
	if err != nil {
		return nil, err
	}
	response, err := client.Post(uri, "application/xml;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"code.google.com/p/go-uuid/uuid"
	"compress/gzip"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/mcuadros/go-version"
	"golang.org/x/sys/unix"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	PRESERVED_CHECKSUM_LENGTH = 64
)

var (
	log = logrus.WithFields(logrus.Fields{"pkg": "util"})
)

func EncodeData(v interface{}) (*bytes.Buffer, error) {
	param := bytes.NewBuffer(nil)
	j, err := json.Marshal(v)
	if err != nil {
		return nil, err
	}
	if _, err := param.Write(j); err != nil {
		return nil, err
	}
	return param, nil
}

func ExtractUUIDs(names []string, prefix, suffix string) ([]string, error) {
	result := []string{}
	for i := range names {
		f := names[i]
		// Remove additional slash if exists
		f = strings.TrimLeft(f, "/")
		f = strings.TrimPrefix(f, prefix)
		f = strings.TrimSuffix(f, suffix)
		if !ValidateUUID(f) {
			return nil, fmt.Errorf("Invalid name %v was processed to extract UUID with prefix %v surfix %v", names[i], prefix, suffix)
		}
		result = append(result, f)
	}
	return result, nil
}

func MkdirIfNotExists(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.MkdirAll(path, os.ModeDir|0700); err != nil {
			return err
		}
	}
	return nil
}

func GetChecksum(data []byte) string {
	checksumBytes := sha512.Sum512(data)
	checksum := hex.EncodeToString(checksumBytes[:])[:PRESERVED_CHECKSUM_LENGTH]
	return checksum
}

func LockFile(fileName string) (*os.File, error) {
	f, err := os.Create(fileName)
	if err != nil {
		return nil, err
	}
	if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
		f.Close()
		return nil, err
	}
	return f, nil
}

func UnlockFile(f *os.File) error {
	defer f.Close()
	if err := unix.Flock(int(f.Fd()), unix.LOCK_UN); err != nil {
		return err
	}
	if _, err := Execute("rm", []string{f.Name()}); err != nil {
		return err
	}
	return nil
}

func SliceToMap(slices []string) map[string]string {
	result := map[string]string{}
	for _, v := range slices {
		pair := strings.Split(v, "=")
		if len(pair) != 2 {
			return nil
		}
		result[pair[0]] = pair[1]
	}
	return result
}

func GetFileChecksum(filePath string) (string, error) {
	output, err := Execute("sha512sum", []string{"-b", filePath})
	if err != nil {
		return "", err
	}
	return strings.Split(string(output), " ")[0], nil
}

func CompressFile(filePath string) error {
	if _, err := Execute("gzip", []string{filePath}); err != nil {
		return err
	}
	return nil
}

func DecompressFile(filePath string) error {
	if _, err := Execute("gunzip", []string{filePath}); err != nil {
		return err
	}
	return nil
}

func CompressDir(sourceDir, targetFile string) error {
	tmpFile := targetFile + ".tmp"
	if _, err := Execute("tar", []string{"cf", tmpFile, "-C", sourceDir, "."}); err != nil {
		return err
	}
	if _, err := Execute("gzip", []string{tmpFile}); err != nil {
		return err
	}
	if _, err := Execute("mv", []string{"-f", tmpFile + ".gz", targetFile}); err != nil {
		return err
	}
	return nil
}

// If sourceFile is inside targetDir, it would be deleted automatically
func DecompressDir(sourceFile, targetDir string) error {
	tmpDir := targetDir + ".tmp"
	if _, err := Execute("rm", []string{"-rf", tmpDir}); err != nil {
		return err
	}
	if err := os.Mkdir(tmpDir, os.ModeDir|0700); err != nil {
		return err
	}
	if _, err := Execute("tar", []string{"xf", sourceFile, "-C", tmpDir}); err != nil {
		return err
	}
	if _, err := Execute("rm", []string{"-rf", targetDir}); err != nil {
		return err
	}
	if _, err := Execute("mv", []string{"-f", tmpDir, targetDir}); err != nil {
		return err
	}
	return nil
}

func Copy(src, dst string) error {
	if _, err := Execute("cp", []string{src, dst}); err != nil {
		return err
	}
	return nil
}

func AttachLoopbackDevice(file string, readonly bool) (string, error) {
	params := []string{"-v", "-f"}
	if readonly {
		params = append(params, "-r")
	}
	params = append(params, file)
	out, err := Execute("losetup", params)
	if err != nil {
		return "", err
	}
	dev := strings.TrimSpace(strings.SplitAfter(string(out[:]), "device is")[1])
	return dev, nil
}

func DetachLoopbackDevice(file, dev string) error {
	output, err := Execute("losetup", []string{dev})
	if err != nil {
		return err
	}
	out := strings.TrimSpace(string(output))
	suffix := "(" + file + ")"
	if !strings.HasSuffix(out, suffix) {
		return fmt.Errorf("Unmatched source file, output %v, suffix %v", out, suffix)
	}
	if _, err := Execute("losetup", []string{"-d", dev}); err != nil {
		return err
	}
	return nil
}

func ValidateUUID(s string) bool {
	return uuid.Parse(s) != nil
}

func CheckUUID(uuid string) error {
	if !ValidateUUID(uuid) {
		return fmt.Errorf("Invalid UUID %v", uuid)
	}
	return nil
}
func ValidateName(name string) bool {
	validName := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`)
	return validName.MatchString(name)
}

func CheckName(name string) error {
	if name == "" {
		return nil
	}
	if !ValidateName(name) {
		return fmt.Errorf("Invalid name %v", name)
	}
	return nil
}

func ParseSize(size string) (int64, error) {
	if size == "" {
		return 0, nil
	}
	size = strings.ToLower(size)
	readableSize := regexp.MustCompile(`^[0-9.]+[kmgt]$`)
	if !readableSize.MatchString(size) {
		value, err := strconv.ParseInt(size, 10, 64)
		return value, err
	}

	last := len(size) - 1
	unit := string(size[last])
	value, err := strconv.ParseInt(size[:last], 10, 64)
	if err != nil {
		return 0, err
	}

	kb := int64(1024)
	mb := 1024 * kb
	gb := 1024 * mb
	tb := 1024 * gb
	switch unit {
	case "k":
		value *= kb
	case "m":
		value *= mb
	case "g":
		value *= gb
	case "t":
		value *= tb
	default:
		return 0, fmt.Errorf("Unrecongized size value %v", size)
	}
	return value, err
}

func CheckBinaryVersion(binaryName, minVersion string, args []string) error {
	output, err := exec.Command(binaryName, args...).CombinedOutput()
	if err != nil {
		return fmt.Errorf("Failed version check for %s, due to %s", binaryName, err.Error())
	}
	v := strings.TrimSpace(string(output))
	if version.Compare(v, minVersion, "<") {
		return fmt.Errorf("Minimal require version for %s is %s, detected %s",
			binaryName, minVersion, v)
	}
	return nil
}

func Execute(binary string, args []string) (string, error) {
	output, err := exec.Command(binary, args...).CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("Failed to execute: %v %v, output %v, error %v", binary, args, string(output), err)
	}
	return string(output), nil
}

func Now() string {
	return time.Now().Format(time.RubyDate)
}

func CompressData(data []byte) (io.ReadSeeker, error) {
	var b bytes.Buffer
	w := gzip.NewWriter(&b)
	if _, err := w.Write(data); err != nil {
		w.Close()
		return nil, err
	}
	w.Close()
	return bytes.NewReader(b.Bytes()), nil
}

func DecompressAndVerify(src io.Reader, checksum string) (io.Reader, error) {
	r, err := gzip.NewReader(src)
	if err != nil {
		return nil, err
	}
	block, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	if GetChecksum(block) != checksum {
		return nil, fmt.Errorf("Checksum verification failed for block!")
	}
	return bytes.NewReader(block), nil
}

func GetUUID(v interface{}, key string, required bool, err error) (string, error) {
	uuid, err := GetFlag(v, key, required, err)
	if err != nil {
		return uuid, err
	}
	uuid = strings.ToLower(uuid)
	if !required && uuid == "" {
		return "", nil
	}
	if err := CheckUUID(uuid); err != nil {
		return "", err
	}
	return uuid, nil
}

func GetName(v interface{}, key string, required bool, err error) (string, error) {
	name, err := GetFlag(v, key, required, err)
	if err != nil {
		return name, err
	}
	if !required && name == "" {
		return name, nil
	}
	if err := CheckName(name); err != nil {
		return "", err
	}
	return name, nil
}

func RequiredMissingError(name string) error {
	return fmt.Errorf("Cannot find valid required parameter:", name)
}

func GetFlag(v interface{}, key string, required bool, err error) (string, error) {
	if err != nil {
		return "", err
	}
	value := ""
	switch v := v.(type) {
	default:
		return "", fmt.Errorf("Unexpected type for getLowerCaseFlag")
	case *cli.Context:
		if key == "" {
			value = v.Args().First()
		} else {
			value = v.String(key)
		}
	case map[string]string:
		value = v[key]
	case *http.Request:
		if err := v.ParseForm(); err != nil {
			return "", err
		}
		value = v.FormValue(key)
	}
	if required && value == "" {
		err = RequiredMissingError(key)
	}
	return value, err
}

func UnescapeURL(url string) string {
	// Deal with escape in url inputed from bash
	result := strings.Replace(url, "\\u0026", "&", 1)
	result = strings.Replace(result, "u0026", "&", 1)
	return result
}

func ValidNetworkAddr(addr string) bool {
	//Is it a IP
	ip := net.ParseIP(addr)
	if ip != nil {
		return true
	}
	//Or host
	ips, err := net.LookupIP(addr)
	if err != nil {
		return false
	}
	if ips == nil || len(ips) == 0 {
		return false
	}
	return true
}
<commit_msg>Timeout when calling external programs<commit_after>package util

import (
	"bytes"
	"code.google.com/p/go-uuid/uuid"
	"compress/gzip"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/mcuadros/go-version"
	"golang.org/x/sys/unix"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	PRESERVED_CHECKSUM_LENGTH = 64
)

var (
	log = logrus.WithFields(logrus.Fields{"pkg": "util"})
)

func EncodeData(v interface{}) (*bytes.Buffer, error) {
	param := bytes.NewBuffer(nil)
	j, err := json.Marshal(v)
	if err != nil {
		return nil, err
	}
	if _, err := param.Write(j); err != nil {
		return nil, err
	}
	return param, nil
}

func ExtractUUIDs(names []string, prefix, suffix string) ([]string, error) {
	result := []string{}
	for i := range names {
		f := names[i]
		// Remove additional slash if exists
		f = strings.TrimLeft(f, "/")
		f = strings.TrimPrefix(f, prefix)
		f = strings.TrimSuffix(f, suffix)
		if !ValidateUUID(f) {
			return nil, fmt.Errorf("Invalid name %v was processed to extract UUID with prefix %v surfix %v", names[i], prefix, suffix)
		}
		result = append(result, f)
	}
	return result, nil
}

func MkdirIfNotExists(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.MkdirAll(path, os.ModeDir|0700); err != nil {
			return err
		}
	}
	return nil
}

func GetChecksum(data []byte) string {
	checksumBytes := sha512.Sum512(data)
	checksum := hex.EncodeToString(checksumBytes[:])[:PRESERVED_CHECKSUM_LENGTH]
	return checksum
}

func LockFile(fileName string) (*os.File, error) {
	f, err := os.Create(fileName)
	if err != nil {
		return nil, err
	}
	if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
		f.Close()
		return nil, err
	}
	return f, nil
}

func UnlockFile(f *os.File) error {
	defer f.Close()
	if err := unix.Flock(int(f.Fd()), unix.LOCK_UN); err != nil {
		return err
	}
	if _, err := Execute("rm", []string{f.Name()}); err != nil {
		return err
	}
	return nil
}

func SliceToMap(slices []string) map[string]string {
	result := map[string]string{}
	for _, v := range slices {
		pair := strings.Split(v, "=")
		if len(pair) != 2 {
			return nil
		}
		result[pair[0]] = pair[1]
	}
	return result
}

func GetFileChecksum(filePath string) (string, error) {
	output, err := Execute("sha512sum", []string{"-b", filePath})
	if err != nil {
		return "", err
	}
	return strings.Split(string(output), " ")[0], nil
}

func CompressFile(filePath string) error {
	if _, err := Execute("gzip", []string{filePath}); err != nil {
		return err
	}
	return nil
}

func DecompressFile(filePath string) error {
	if _, err := Execute("gunzip", []string{filePath}); err != nil {
		return err
	}
	return nil
}

func CompressDir(sourceDir, targetFile string) error {
	tmpFile := targetFile + ".tmp"
	if _, err := Execute("tar", []string{"cf", tmpFile, "-C", sourceDir, "."}); err != nil {
		return err
	}
	if _, err := Execute("gzip", []string{tmpFile}); err != nil {
		return err
	}
	if _, err := Execute("mv", []string{"-f", tmpFile + ".gz", targetFile}); err != nil {
		return err
	}
	return nil
}

// If sourceFile is inside targetDir, it would be deleted automatically
func DecompressDir(sourceFile, targetDir string) error {
	tmpDir := targetDir + ".tmp"
	if _, err := Execute("rm", []string{"-rf", tmpDir}); err != nil {
		return err
	}
	if err := os.Mkdir(tmpDir, os.ModeDir|0700); err != nil {
		return err
	}
	if _, err := Execute("tar", []string{"xf", sourceFile, "-C", tmpDir}); err != nil {
		return err
	}
	if _, err := Execute("rm", []string{"-rf", targetDir}); err != nil {
		return err
	}
	if _, err := Execute("mv", []string{"-f", tmpDir, targetDir}); err != nil {
		return err
	}
	return nil
}

func Copy(src, dst string) error {
	if _, err := Execute("cp", []string{src, dst}); err != nil {
		return err
	}
	return nil
}

func AttachLoopbackDevice(file string, readonly bool) (string, error) {
	params := []string{"-v", "-f"}
	if readonly {
		params = append(params, "-r")
	}
	params = append(params, file)
	out, err := Execute("losetup", params)
	if err != nil {
		return "", err
	}
	dev := strings.TrimSpace(strings.SplitAfter(string(out[:]), "device is")[1])
	return dev, nil
}

func DetachLoopbackDevice(file, dev string) error {
	output, err := Execute("losetup", []string{dev})
	if err != nil {
		return err
	}
	out := strings.TrimSpace(string(output))
	suffix := "(" + file + ")"
	if !strings.HasSuffix(out, suffix) {
		return fmt.Errorf("Unmatched source file, output %v, suffix %v", out, suffix)
	}
	if _, err := Execute("losetup", []string{"-d", dev}); err != nil {
		return err
	}
	return nil
}

func ValidateUUID(s string) bool {
	return uuid.Parse(s) != nil
}

func CheckUUID(uuid string) error {
	if !ValidateUUID(uuid) {
		return fmt.Errorf("Invalid UUID %v", uuid)
	}
	return nil
}
func ValidateName(name string) bool {
	validName := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`)
	return validName.MatchString(name)
}

func CheckName(name string) error {
	if name == "" {
		return nil
	}
	if !ValidateName(name) {
		return fmt.Errorf("Invalid name %v", name)
	}
	return nil
}

func ParseSize(size string) (int64, error) {
	if size == "" {
		return 0, nil
	}
	size = strings.ToLower(size)
	readableSize := regexp.MustCompile(`^[0-9.]+[kmgt]$`)
	if !readableSize.MatchString(size) {
		value, err := strconv.ParseInt(size, 10, 64)
		return value, err
	}

	last := len(size) - 1
	unit := string(size[last])
	value, err := strconv.ParseInt(size[:last], 10, 64)
	if err != nil {
		return 0, err
	}

	kb := int64(1024)
	mb := 1024 * kb
	gb := 1024 * mb
	tb := 1024 * gb
	switch unit {
	case "k":
		value *= kb
	case "m":
		value *= mb
	case "g":
		value *= gb
	case "t":
		value *= tb
	default:
		return 0, fmt.Errorf("Unrecongized size value %v", size)
	}
	return value, err
}

func CheckBinaryVersion(binaryName, minVersion string, args []string) error {
	output, err := exec.Command(binaryName, args...).CombinedOutput()
	if err != nil {
		return fmt.Errorf("Failed version check for %s, due to %s", binaryName, err.Error())
	}
	v := strings.TrimSpace(string(output))
	if version.Compare(v, minVersion, "<") {
		return fmt.Errorf("Minimal require version for %s is %s, detected %s",
			binaryName, minVersion, v)
	}
	return nil
}

func Execute(binary string, args []string) (string, error) {
	var output []byte
	var err error
	cmd := exec.Command(binary, args...)
	done := make(chan struct{})

	go func() {
		output, err = cmd.CombinedOutput()
		done <- struct{}{}
	}()

	select {
	case <-done:
	case <-time.After(1 * time.Minute):
		if cmd.Process != nil {
			cmd.Process.Kill()
		}
		return "", fmt.Errorf("Timeout executing: %v %v, output %v, error %v", binary, args, string(output), err)
	}

	if err != nil {
		return "", fmt.Errorf("Failed to execute: %v %v, output %v, error %v", binary, args, string(output), err)
	}
	return string(output), nil
}

func Now() string {
	return time.Now().Format(time.RubyDate)
}

func CompressData(data []byte) (io.ReadSeeker, error) {
	var b bytes.Buffer
	w := gzip.NewWriter(&b)
	if _, err := w.Write(data); err != nil {
		w.Close()
		return nil, err
	}
	w.Close()
	return bytes.NewReader(b.Bytes()), nil
}

func DecompressAndVerify(src io.Reader, checksum string) (io.Reader, error) {
	r, err := gzip.NewReader(src)
	if err != nil {
		return nil, err
	}
	block, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	if GetChecksum(block) != checksum {
		return nil, fmt.Errorf("Checksum verification failed for block!")
	}
	return bytes.NewReader(block), nil
}

func GetUUID(v interface{}, key string, required bool, err error) (string, error) {
	uuid, err := GetFlag(v, key, required, err)
	if err != nil {
		return uuid, err
	}
	uuid = strings.ToLower(uuid)
	if !required && uuid == "" {
		return "", nil
	}
	if err := CheckUUID(uuid); err != nil {
		return "", err
	}
	return uuid, nil
}

func GetName(v interface{}, key string, required bool, err error) (string, error) {
	name, err := GetFlag(v, key, required, err)
	if err != nil {
		return name, err
	}
	if !required && name == "" {
		return name, nil
	}
	if err := CheckName(name); err != nil {
		return "", err
	}
	return name, nil
}

func RequiredMissingError(name string) error {
	return fmt.Errorf("Cannot find valid required parameter:", name)
}

func GetFlag(v interface{}, key string, required bool, err error) (string, error) {
	if err != nil {
		return "", err
	}
	value := ""
	switch v := v.(type) {
	default:
		return "", fmt.Errorf("Unexpected type for getLowerCaseFlag")
	case *cli.Context:
		if key == "" {
			value = v.Args().First()
		} else {
			value = v.String(key)
		}
	case map[string]string:
		value = v[key]
	case *http.Request:
		if err := v.ParseForm(); err != nil {
			return "", err
		}
		value = v.FormValue(key)
	}
	if required && value == "" {
		err = RequiredMissingError(key)
	}
	return value, err
}

func UnescapeURL(url string) string {
	// Deal with escape in url inputed from bash
	result := strings.Replace(url, "\\u0026", "&", 1)
	result = strings.Replace(result, "u0026", "&", 1)
	return result
}

func ValidNetworkAddr(addr string) bool {
	//Is it a IP
	ip := net.ParseIP(addr)
	if ip != nil {
		return true
	}
	//Or host
	ips, err := net.LookupIP(addr)
	if err != nil {
		return false
	}
	if ips == nil || len(ips) == 0 {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package strconvutil

import (
	"fmt"
	"math"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

// AtoiWithDefault is like Atoi but takes a default value
// which it returns in the event of a parse error.
func AtoiWithDefault(s string, def int) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		return def
	}
	return i
}

// Commify takes an int64 and adds comma for every thousand
// Stack Overflow: http://stackoverflow.com/users/1705598/icza
// URL: http://stackoverflow.com/questions/13020308/how-to-fmt-printf-an-integer-with-thousands-comma
func Commify(n int64) string {
	in := strconv.FormatInt(n, 10)
	out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)
	if in[0] == '-' {
		in, out[0] = in[1:], '-'
	}

	for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
		out[j] = in[i]
		if i == 0 {
			return string(out)
		}
		if k++; k == 3 {
			j, k = j-1, 0
			out[j] = ','
		}
	}
}

var RxPlus = regexp.MustCompile(`^\+`)

func MustParseE164ToInt(s string) int {
	s = strings.TrimSpace(s)
	s = RxPlus.ReplaceAllString(s, "")
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	return i
}

func MustParseInt(s string) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	return i
}

func MustParseBool(s string) bool {
	parsed, err := strconv.ParseBool(s)
	if err != nil {
		return false
	}
	return parsed
}

// SliceStringToInt converts a slice of string integers.
func SliceStringToInt(strings []string) ([]int, error) {
	ints := []int{}
	for _, s := range strings {
		thisInt, err := strconv.Atoi(s)
		if err != nil {
			return ints, err
		}
		ints = append(ints, thisInt)
	}
	return ints, nil
}

// SliceStringToIntSort converts and sorts a slice of string integers.
func SliceStringToIntSort(strings []string) ([]int, error) {
	ints, err := SliceStringToInt(strings)
	if err != nil {
		return ints, err
	}
	intSlice := sort.IntSlice(ints)
	intSlice.Sort()
	return intSlice, nil
}

func FormatFloat64ToIntStringFunnel(v float64) string {
	return FormatFloat64ToAnyStringFunnel(v, `%0.0f%%`)
}

// FormatFloat64ToAnyStringFunnel is used for funnels.
func FormatFloat64ToAnyStringFunnel(v float64, pattern string) string {
	return fmt.Sprintf(pattern, ChangeToFunnelPct(v))
}

func FormatFloat64ToIntString(v float64) string {
	return FormatFloat64ToAnyString(v, `%0.0f%%`)
}

// FormatFloat64ToAnyString is used for XoX growth.
func FormatFloat64ToAnyString(v float64, pattern string) string {
	return fmt.Sprintf(pattern, ChangeToXoXPct(v))
}

// ChangeToXoXPct converts a 1.0 == 100% based `float64` to a
// XoX percentage `float64`.
func ChangeToXoXPct(v float64) float64 {
	if v < 1.0 {
		return -1 * 100.0 * (1.0 - v)
	}
	return 100.0 * (v - 1.0)
}

// ChangeToFunnelPct converts a 1.0 == 100% based `float64` to a
// Funnel percentage `float64`.
func ChangeToFunnelPct(v float64) float64 { return v * 100.0 }

// Int64Len returns the length of an Int64 number.
func Int64Len(val int64) int {
	return len(fmt.Sprintf("%d", val))
}

// Int64Abbreviation returns integer abbreviations. For example,
// "1.5K", "15K", "150K", "1.5M", "15M", "150M".
func Int64Abbreviation(val int64) string {
	if val <= 999 {
		return strconv.Itoa(int(val))
	}
	valStr := fmt.Sprintf("%d", val)
	valLen := len(valStr)
	switch valLen {
	case 4:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fK", float)
	case 5:
		return fmt.Sprintf("%sK", valStr[0:2])
	case 6:
		return fmt.Sprintf("%sK", valStr[0:3])
	case 7:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fM", float)
	case 8:
		return fmt.Sprintf("%sM", valStr[0:2])
	case 9:
		return fmt.Sprintf("%sM", valStr[0:3])
	case 10:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fB", float)
	case 11:
		return fmt.Sprintf("%sB", valStr[0:2])
	case 12:
		return fmt.Sprintf("%sB", valStr[0:3])
	case 13:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fT", float)
	case 14:
		return fmt.Sprintf("%sT", valStr[0:2])
	case 15:
		return fmt.Sprintf("%sT", valStr[0:3])
	}
	return fmt.Sprintf("%d", val)
}
<commit_msg>add strconv.Int64ToString function definition<commit_after>package strconvutil

import (
	"fmt"
	"math"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

// AtoiWithDefault is like Atoi but takes a default value
// which it returns in the event of a parse error.
func AtoiWithDefault(s string, def int) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		return def
	}
	return i
}

// Int64ToString is a function type to define functions.
type Int64ToString func(int64) string

// Commify takes an int64 and adds comma for every thousand
// Stack Overflow: http://stackoverflow.com/users/1705598/icza
// URL: http://stackoverflow.com/questions/13020308/how-to-fmt-printf-an-integer-with-thousands-comma
func Commify(n int64) string {
	in := strconv.FormatInt(n, 10)
	out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)
	if in[0] == '-' {
		in, out[0] = in[1:], '-'
	}

	for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
		out[j] = in[i]
		if i == 0 {
			return string(out)
		}
		if k++; k == 3 {
			j, k = j-1, 0
			out[j] = ','
		}
	}
}

var RxPlus = regexp.MustCompile(`^\+`)

func MustParseE164ToInt(s string) int {
	s = strings.TrimSpace(s)
	s = RxPlus.ReplaceAllString(s, "")
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	return i
}

func MustParseInt(s string) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	return i
}

func MustParseBool(s string) bool {
	parsed, err := strconv.ParseBool(s)
	if err != nil {
		return false
	}
	return parsed
}

// SliceStringToInt converts a slice of string integers.
func SliceStringToInt(strings []string) ([]int, error) {
	ints := []int{}
	for _, s := range strings {
		thisInt, err := strconv.Atoi(s)
		if err != nil {
			return ints, err
		}
		ints = append(ints, thisInt)
	}
	return ints, nil
}

// SliceStringToIntSort converts and sorts a slice of string integers.
func SliceStringToIntSort(strings []string) ([]int, error) {
	ints, err := SliceStringToInt(strings)
	if err != nil {
		return ints, err
	}
	intSlice := sort.IntSlice(ints)
	intSlice.Sort()
	return intSlice, nil
}

func FormatFloat64ToIntStringFunnel(v float64) string {
	return FormatFloat64ToAnyStringFunnel(v, `%0.0f%%`)
}

// FormatFloat64ToAnyStringFunnel is used for funnels.
func FormatFloat64ToAnyStringFunnel(v float64, pattern string) string {
	return fmt.Sprintf(pattern, ChangeToFunnelPct(v))
}

func FormatFloat64ToIntString(v float64) string {
	return FormatFloat64ToAnyString(v, `%0.0f%%`)
}

// FormatFloat64ToAnyString is used for XoX growth.
func FormatFloat64ToAnyString(v float64, pattern string) string {
	return fmt.Sprintf(pattern, ChangeToXoXPct(v))
}

// ChangeToXoXPct converts a 1.0 == 100% based `float64` to a
// XoX percentage `float64`.
func ChangeToXoXPct(v float64) float64 {
	if v < 1.0 {
		return -1 * 100.0 * (1.0 - v)
	}
	return 100.0 * (v - 1.0)
}

// ChangeToFunnelPct converts a 1.0 == 100% based `float64` to a
// Funnel percentage `float64`.
func ChangeToFunnelPct(v float64) float64 { return v * 100.0 }

// Int64Len returns the length of an Int64 number.
func Int64Len(val int64) int {
	return len(fmt.Sprintf("%d", val))
}

// Int64Abbreviation returns integer abbreviations. For example,
// "1.5K", "15K", "150K", "1.5M", "15M", "150M".
func Int64Abbreviation(val int64) string {
	if val <= 999 {
		return strconv.Itoa(int(val))
	}
	valStr := fmt.Sprintf("%d", val)
	valLen := len(valStr)
	switch valLen {
	case 4:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fK", float)
	case 5:
		return fmt.Sprintf("%sK", valStr[0:2])
	case 6:
		return fmt.Sprintf("%sK", valStr[0:3])
	case 7:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fM", float)
	case 8:
		return fmt.Sprintf("%sM", valStr[0:2])
	case 9:
		return fmt.Sprintf("%sM", valStr[0:3])
	case 10:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fB", float)
	case 11:
		return fmt.Sprintf("%sB", valStr[0:2])
	case 12:
		return fmt.Sprintf("%sB", valStr[0:3])
	case 13:
		float := float64(val) / math.Pow10(valLen-1)
		return fmt.Sprintf("%.1fT", float)
	case 14:
		return fmt.Sprintf("%sT", valStr[0:2])
	case 15:
		return fmt.Sprintf("%sT", valStr[0:3])
	}
	return fmt.Sprintf("%d", val)
}
<|endoftext|>


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

package weed

import "errors"

var (
	// ErrSeekPositionNotSupported occurs when a syncable type receives a
	// seek destination which it can not fulfil to. These syncable types
	// usually only support seeking to the beginning or to the end, as they
	// write to and read from multiple data files.
	ErrSeekPositionNotSupported = errors.New("Seek position is not supported.")
)

// Options represents syncing configuration options.
type Options struct {
	// MinSize is used to determine the number of megabytes to write to
	// a file before the file should be rotated, and a new file used. This
	// is only available on certain types which support rotation of files,
	// but do not support appendable writes. The file is overwritten on
	// each write using the buffered data. MinSize should be specified in
	// megabytes.
	MinSize int
	// BufferWrites can be used to determine whether writes are buffered
	// up to a certain number of bytes, before being synced. This is only
	// available on syncable types which do not support appendable writes.
	// BufferWrites should be specified in megabytes.
	BufferWrites int
}

type Storage struct {
}

func New(path string, opts *Options) (*Storage, error) {
	return nil, nil
}

func (s *Storage) Close() error {
	return nil
}

func (s *Storage) Read(p []byte) (int, error) {
	return 0, nil
}

func (s *Storage) Write(p []byte) (int, error) {
	return 0, nil
}

func (s *Storage) Seek(offset int64, whence int) (int64, error) {
	return 0, nil
}

func (s *Storage) Sync() error {
	return nil
}
<commit_msg>Add SeaweedFS basic instance initialisation<commit_after>// Copyright © 2016 Abcum Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package weed

import "errors"

var (
	// ErrSeekPositionNotSupported occurs when a syncable type receives a
	// seek destination which it can not fulfil to. These syncable types
	// usually only support seeking to the beginning or to the end, as they
	// write to and read from multiple data files.
	ErrSeekPositionNotSupported = errors.New("Seek position is not supported.")
)

// Options represents syncing configuration options.
type Options struct {
	// MinSize is used to determine the number of megabytes to write to
	// a file before the file should be rotated, and a new file used. This
	// is only available on certain types which support rotation of files,
	// but do not support appendable writes. The file is overwritten on
	// each write using the buffered data. MinSize should be specified in
	// megabytes.
	MinSize int
	// BufferWrites can be used to determine whether writes are buffered
	// up to a certain number of bytes, before being synced. This is only
	// available on syncable types which do not support appendable writes.
	// BufferWrites should be specified in megabytes.
	BufferWrites int
}

type Storage struct {
	opts *Options
}

func New(path string, opts *Options) (*Storage, error) {

	var s *Storage
	var e error

	s.opts = opts

	return s, e

}

func (s *Storage) Close() error {
	return nil
}

func (s *Storage) Read(p []byte) (int, error) {
	return 0, nil
}

func (s *Storage) Write(p []byte) (int, error) {
	return 0, nil
}

func (s *Storage) Seek(offset int64, whence int) (int64, error) {
	return 0, nil
}

func (s *Storage) Sync() error {
	return nil
}
<|endoftext|>