<commit_before>package gcm

import (
	"encoding/json"

	log "github.com/Sirupsen/logrus"
	"github.com/alexjlockwood/gcm"

	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/cluster"
	"github.com/smancke/guble/server/kvstore"
	"github.com/smancke/guble/server/router"

	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

const (
	// registrationsSchema is the default sqlite schema for GCM
	schema = "gcm_registration"

	// sendRetries is the number of retries when sending a message
	sendRetries = 5

	subscribePrefixPath = "subscribe"

	// default channel buffer size
	bufferSize = 1000

	syncPath = protocol.Path("/gcm/sync")
)

var logger = log.WithFields(log.Fields{
	"app":    "guble",
	"env":    "TBD",
	"module": "gcm",
})

// Connector is the structure for handling the communication with Google Cloud Messaging
type Connector struct {
	Sender        *gcm.Sender
	router        router.Router
	cluster       *cluster.Cluster
	kvStore       kvstore.KVStore
	prefix        string
	pipelineC     chan *pipeMessage
	stopC         chan bool
	nWorkers      int
	wg            sync.WaitGroup
	broadcastPath string
}

// New creates a new *Connector without starting it
func New(router router.Router, prefix string, gcmAPIKey string, nWorkers int) (*Connector, error) {
	kvStore, err := router.KVStore()
	if err != nil {
		return nil, err
	}

	return &Connector{
		Sender:        &gcm.Sender{ApiKey: gcmAPIKey},
		router:        router,
		cluster:       router.Cluster(),
		kvStore:       kvStore,
		prefix:        prefix,
		pipelineC:     make(chan *pipeMessage, bufferSize),
		stopC:         make(chan bool),
		nWorkers:      nWorkers,
		broadcastPath: removeTrailingSlash(prefix) + "/broadcast",
	}, nil
}

// Start opens the connector, creates more goroutines / workers to handle messages coming from the router
func (conn *Connector) Start() error {
	resetGcmMetrics()

	// start subscription sync loop if we are in cluster mode
	if conn.cluster != nil {
		if err := conn.syncLoop(); err != nil {
			return err
		}
	}

	// blocking until current subs are loaded
	conn.loadSubscriptions()

	go func() {
		for id := 1; id <= conn.nWorkers; id++ {
			go conn.loopPipeline(id)
		}
	}()
	return nil
}

// Stop signals the closing of GCMConnector
func (conn *Connector) Stop() error {
	logger.Debug("Stopping ...")
	close(conn.stopC)
	conn.wg.Wait()
	logger.Debug("Stopped")
	return nil
}

// Check returns nil if health-check succeeds, or an error if health-check fails
// by sending a request with only apikey. If the response is processed by the GCM endpoint
// the gcmStatus will be UP, otherwise the error from sending the message will be returned.
func (conn *Connector) Check() error {
	payload := messageMap(&protocol.Message{Body: []byte(`{"registration_ids":["ABC"]}`)})
	_, err := conn.Sender.Send(gcm.NewMessage(payload, ""), sendRetries)
	if err != nil {
		logger.WithError(err).Error("Error sending ping message")
		return err
	}
	return nil
}

// loopPipeline awaits in a loop for messages subscriptions to be forwarded to GCM,
// until the stop-channel is closed
func (conn *Connector) loopPipeline(id int) {
	conn.wg.Add(1)
	defer func() {
		logger.WithField("id", id).Debug("Worker stopped")
		conn.wg.Done()
	}()
	logger.WithField("id", id).Debug("Worker started")

	for {
		select {
		case pm := <-conn.pipelineC:
			// only forward to gcm message which have subscription on this node and not the other received from cluster
			if pm != nil {
				if conn.cluster != nil && conn.cluster.Config.ID != pm.message.NodeID {
					pm.ignoreMessage()
					continue
				}

				conn.sendMessage(pm)

			}
		case <-conn.stopC:
			return
		}
	}
}

func (conn *Connector) sendMessage(pm *pipeMessage) {
	gcmID := pm.subscription.route.Get(applicationIDKey)
	payload := pm.payload()

	gcmMessage := gcm.NewMessage(payload, gcmID)
	logger.WithFields(log.Fields{
		"gcmID":      gcmID,
		"pipeLength": len(conn.pipelineC),
	}).Debug("Sending message")

	result, err := conn.Sender.Send(gcmMessage, sendRetries)
	if err != nil {
		pm.errC <- err
		mTotalSentMessageErrors.Add(1)
		return
	}
	pm.resultC <- result
	mTotalSentMessages.Add(1)
}

// GetPrefix is used to satisfy the HTTP handler interface
func (conn *Connector) GetPrefix() string {
	return conn.prefix
}

// ServeHTTP handles the subscription in GCM
func (conn *Connector) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		logger.WithField("method", r.Method).Error("Only HTTP post method supported.")
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	userID, gcmID, topic, err := conn.parseParams(r.URL.Path)
	if err != nil {
		http.Error(w, "Invalid Parameters in request", http.StatusBadRequest)
		return
	}
	initSubscription(conn, topic, userID, gcmID, 0, true)

	// synchronize subscription after storing if cluster exists
	conn.synchronizeSubscription(topic, userID, gcmID)

	fmt.Fprintf(w, "registered: %v\n", topic)
}

// parseParams will parse the HTTP URL with format /gcm/:userid/:gcmid/subscribe/*topic
// returning the parsed Params, or error if the request is not in the correct format
func (conn *Connector) parseParams(path string) (userID, gcmID, topic string, err error) {
	currentURLPath := removeTrailingSlash(path)

	if strings.HasPrefix(currentURLPath, conn.prefix) != true {
		err = errors.New("gcm: GCM request is not starting with gcm prefix")
		return
	}
	pathAfterPrefix := strings.TrimPrefix(currentURLPath, conn.prefix)

	splitParams := strings.SplitN(pathAfterPrefix, "/", 3)
	if len(splitParams) != 3 {
		err = errors.New("gcm: GCM request has wrong number of params")
		return
	}
	userID = splitParams[0]
	gcmID = splitParams[1]

	if strings.HasPrefix(splitParams[2], subscribePrefixPath+"/") != true {
		err = errors.New("gcm: GCM request third param is not subscribe")
		return
	}
	topic = strings.TrimPrefix(splitParams[2], subscribePrefixPath)
	return userID, gcmID, topic, nil
}

func (conn *Connector) loadSubscriptions() {
	subscriptions := conn.kvStore.Iterate(schema, "")
	count := 0
	for {
		select {
		case entry, ok := <-subscriptions:
			if !ok {
				logger.WithField("count", count).Info("Loaded GCM subscriptions")
				return
			}
			conn.loadSubscription(entry)
			count++
		}
	}
}

// loadSubscription loads a kvstore entry and creates a subscription from it
func (conn *Connector) loadSubscription(entry [2]string) {
	gcmID := entry[0]
	values := strings.Split(entry[1], ":")
	userID := values[0]
	topic := values[1]
	lastID, err := strconv.ParseUint(values[2], 10, 64)
	if err != nil {
		lastID = 0
	}

	initSubscription(conn, topic, userID, gcmID, lastID, false)

	logger.WithFields(log.Fields{
		"gcmID":  gcmID,
		"userID": userID,
		"topic":  topic,
	}).Debug("Loaded GCM subscription")
}

// Creates a route and listens for subscription synchronization
func (conn *Connector) syncLoop() error {
	r := router.NewRoute(router.RouteConfig{
		Path:        syncPath,
		ChannelSize: 100,
	})
	_, err := conn.router.Subscribe(r)
	if err != nil {
		return err
	}

	go func() {
		logger.Info("Sync loop starting")
		conn.wg.Add(1)

		defer func() {
			logger.Info("Sync loop stopped")
			conn.wg.Done()
		}()

		for {
			select {
			case m, opened := <-r.MessagesChannel():
				if !opened {
					logger.Error("Sync loop channel closed")
					return
				}

				if m.NodeID == conn.cluster.Config.ID {
					logger.Debug("Received own subscription loop")
					continue
				}

				subscriptionSync, err := (&subscriptionSync{}).Decode(m.Body)
				if err != nil {
					logger.WithError(err).Error("Error decoding subscription sync")
					continue
				}

				logger.Debug("Initializing sync subscription without storing it.")
				if _, err := initSubscription(
					conn,
					subscriptionSync.Topic,
					subscriptionSync.UserID,
					subscriptionSync.GCMID,
					0,
					false); err != nil {
					logger.WithError(err).Error("Error synchronizing subscription")
				}
			case <-conn.stopC:
				return
			}
		}
	}()

	return nil
}

func (conn *Connector) synchronizeSubscription(topic, userID, gcmID string) error {
	// there is no cluster setup, no need for synchronization of subscription
	if conn.cluster == nil {
		return nil
	}

	data, err := (&subscriptionSync{topic, userID, gcmID}).Encode()
	if err != nil {
		return err
	}

	return conn.router.HandleMessage(&protocol.Message{
		Path: syncPath,
		Body: data,
	})
}

// used to sync subscriptions with other nodes
type subscriptionSync struct {
	Topic  string
	UserID string
	GCMID  string
}

func (s *subscriptionSync) Encode() ([]byte, error) {
	return json.Marshal(s)
}

func (s *subscriptionSync) Decode(data []byte) (*subscriptionSync, error) {
	if err := json.Unmarshal(data, s); err != nil {
		return nil, err
	}
	return s, nil
}

func removeTrailingSlash(path string) string {
	if len(path) > 1 && path[len(path)-1] == '/' {
		return path[:len(path)-1]
	}
	return path
}
<commit_msg>simplified ifs<commit_after>package gcm

import (
	"encoding/json"

	log "github.com/Sirupsen/logrus"
	"github.com/alexjlockwood/gcm"

	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/cluster"
	"github.com/smancke/guble/server/kvstore"
	"github.com/smancke/guble/server/router"

	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

const (
	// registrationsSchema is the default sqlite schema for GCM
	schema = "gcm_registration"

	// sendRetries is the number of retries when sending a message
	sendRetries = 5

	subscribePrefixPath = "subscribe"

	// default channel buffer size
	bufferSize = 1000

	syncPath = protocol.Path("/gcm/sync")
)

var logger = log.WithFields(log.Fields{
	"app":    "guble",
	"env":    "TBD",
	"module": "gcm",
})

// Connector is the structure for handling the communication with Google Cloud Messaging
type Connector struct {
	Sender        *gcm.Sender
	router        router.Router
	cluster       *cluster.Cluster
	kvStore       kvstore.KVStore
	prefix        string
	pipelineC     chan *pipeMessage
	stopC         chan bool
	nWorkers      int
	wg            sync.WaitGroup
	broadcastPath string
}

// New creates a new *Connector without starting it
func New(router router.Router, prefix string, gcmAPIKey string, nWorkers int) (*Connector, error) {
	kvStore, err := router.KVStore()
	if err != nil {
		return nil, err
	}

	return &Connector{
		Sender:        &gcm.Sender{ApiKey: gcmAPIKey},
		router:        router,
		cluster:       router.Cluster(),
		kvStore:       kvStore,
		prefix:        prefix,
		pipelineC:     make(chan *pipeMessage, bufferSize),
		stopC:         make(chan bool),
		nWorkers:      nWorkers,
		broadcastPath: removeTrailingSlash(prefix) + "/broadcast",
	}, nil
}

// Start opens the connector, creates more goroutines / workers to handle messages coming from the router
func (conn *Connector) Start() error {
	resetGcmMetrics()

	// start subscription sync loop if we are in cluster mode
	if conn.cluster != nil {
		if err := conn.syncLoop(); err != nil {
			return err
		}
	}

	// blocking until current subs are loaded
	conn.loadSubscriptions()

	go func() {
		for id := 1; id <= conn.nWorkers; id++ {
			go conn.loopPipeline(id)
		}
	}()
	return nil
}

// Stop signals the closing of GCMConnector
func (conn *Connector) Stop() error {
	logger.Debug("Stopping ...")
	close(conn.stopC)
	conn.wg.Wait()
	logger.Debug("Stopped")
	return nil
}

// Check returns nil if health-check succeeds, or an error if health-check fails
// by sending a request with only apikey. If the response is processed by the GCM endpoint
// the gcmStatus will be UP, otherwise the error from sending the message will be returned.
func (conn *Connector) Check() error {
	payload := messageMap(&protocol.Message{Body: []byte(`{"registration_ids":["ABC"]}`)})
	_, err := conn.Sender.Send(gcm.NewMessage(payload, ""), sendRetries)
	if err != nil {
		logger.WithError(err).Error("Error sending ping message")
		return err
	}
	return nil
}

// loopPipeline awaits in a loop for messages subscriptions to be forwarded to GCM,
// until the stop-channel is closed
func (conn *Connector) loopPipeline(id int) {
	conn.wg.Add(1)
	defer func() {
		logger.WithField("id", id).Debug("Worker stopped")
		conn.wg.Done()
	}()
	logger.WithField("id", id).Debug("Worker started")

	for {
		select {
		case pm := <-conn.pipelineC:
			// only forward to gcm message which have subscription on this node and not the other received from cluster
			if pm != nil {
				if conn.cluster != nil && conn.cluster.Config.ID != pm.message.NodeID {
					pm.ignoreMessage()
					continue
				}

				conn.sendMessage(pm)

			}
		case <-conn.stopC:
			return
		}
	}
}

func (conn *Connector) sendMessage(pm *pipeMessage) {
	gcmID := pm.subscription.route.Get(applicationIDKey)
	payload := pm.payload()

	gcmMessage := gcm.NewMessage(payload, gcmID)
	logger.WithFields(log.Fields{
		"gcmID":      gcmID,
		"pipeLength": len(conn.pipelineC),
	}).Debug("Sending message")

	result, err := conn.Sender.Send(gcmMessage, sendRetries)
	if err != nil {
		pm.errC <- err
		mTotalSentMessageErrors.Add(1)
		return
	}
	pm.resultC <- result
	mTotalSentMessages.Add(1)
}

// GetPrefix is used to satisfy the HTTP handler interface
func (conn *Connector) GetPrefix() string {
	return conn.prefix
}

// ServeHTTP handles the subscription in GCM
func (conn *Connector) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		logger.WithField("method", r.Method).Error("Only HTTP post method supported.")
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	userID, gcmID, topic, err := conn.parseParams(r.URL.Path)
	if err != nil {
		http.Error(w, "Invalid Parameters in request", http.StatusBadRequest)
		return
	}
	initSubscription(conn, topic, userID, gcmID, 0, true)

	// synchronize subscription after storing if cluster exists
	conn.synchronizeSubscription(topic, userID, gcmID)

	fmt.Fprintf(w, "registered: %v\n", topic)
}

// parseParams will parse the HTTP URL with format /gcm/:userid/:gcmid/subscribe/*topic
// returning the parsed Params, or error if the request is not in the correct format
func (conn *Connector) parseParams(path string) (userID, gcmID, topic string, err error) {
	currentURLPath := removeTrailingSlash(path)

	if !strings.HasPrefix(currentURLPath, conn.prefix) {
		err = errors.New("gcm: GCM request is not starting with gcm prefix")
		return
	}
	pathAfterPrefix := strings.TrimPrefix(currentURLPath, conn.prefix)

	splitParams := strings.SplitN(pathAfterPrefix, "/", 3)
	if len(splitParams) != 3 {
		err = errors.New("gcm: GCM request has wrong number of params")
		return
	}
	userID = splitParams[0]
	gcmID = splitParams[1]

	if !strings.HasPrefix(splitParams[2], subscribePrefixPath+"/") {
		err = errors.New("gcm: GCM request third param is not subscribe")
		return
	}
	topic = strings.TrimPrefix(splitParams[2], subscribePrefixPath)
	return userID, gcmID, topic, nil
}

func (conn *Connector) loadSubscriptions() {
	subscriptions := conn.kvStore.Iterate(schema, "")
	count := 0
	for {
		select {
		case entry, ok := <-subscriptions:
			if !ok {
				logger.WithField("count", count).Info("Loaded GCM subscriptions")
				return
			}
			conn.loadSubscription(entry)
			count++
		}
	}
}

// loadSubscription loads a kvstore entry and creates a subscription from it
func (conn *Connector) loadSubscription(entry [2]string) {
	gcmID := entry[0]
	values := strings.Split(entry[1], ":")
	userID := values[0]
	topic := values[1]
	lastID, err := strconv.ParseUint(values[2], 10, 64)
	if err != nil {
		lastID = 0
	}

	initSubscription(conn, topic, userID, gcmID, lastID, false)

	logger.WithFields(log.Fields{
		"gcmID":  gcmID,
		"userID": userID,
		"topic":  topic,
	}).Debug("Loaded GCM subscription")
}

// Creates a route and listens for subscription synchronization
func (conn *Connector) syncLoop() error {
	r := router.NewRoute(router.RouteConfig{
		Path:        syncPath,
		ChannelSize: 100,
	})
	_, err := conn.router.Subscribe(r)
	if err != nil {
		return err
	}

	go func() {
		logger.Info("Sync loop starting")
		conn.wg.Add(1)

		defer func() {
			logger.Info("Sync loop stopped")
			conn.wg.Done()
		}()

		for {
			select {
			case m, opened := <-r.MessagesChannel():
				if !opened {
					logger.Error("Sync loop channel closed")
					return
				}

				if m.NodeID == conn.cluster.Config.ID {
					logger.Debug("Received own subscription loop")
					continue
				}

				subscriptionSync, err := (&subscriptionSync{}).Decode(m.Body)
				if err != nil {
					logger.WithError(err).Error("Error decoding subscription sync")
					continue
				}

				logger.Debug("Initializing sync subscription without storing it.")
				if _, err := initSubscription(
					conn,
					subscriptionSync.Topic,
					subscriptionSync.UserID,
					subscriptionSync.GCMID,
					0,
					false); err != nil {
					logger.WithError(err).Error("Error synchronizing subscription")
				}
			case <-conn.stopC:
				return
			}
		}
	}()

	return nil
}

func (conn *Connector) synchronizeSubscription(topic, userID, gcmID string) error {
	// there is no cluster setup, no need for synchronization of subscription
	if conn.cluster == nil {
		return nil
	}

	data, err := (&subscriptionSync{topic, userID, gcmID}).Encode()
	if err != nil {
		return err
	}

	return conn.router.HandleMessage(&protocol.Message{
		Path: syncPath,
		Body: data,
	})
}

// used to sync subscriptions with other nodes
type subscriptionSync struct {
	Topic  string
	UserID string
	GCMID  string
}

func (s *subscriptionSync) Encode() ([]byte, error) {
	return json.Marshal(s)
}

func (s *subscriptionSync) Decode(data []byte) (*subscriptionSync, error) {
	if err := json.Unmarshal(data, s); err != nil {
		return nil, err
	}
	return s, nil
}

func removeTrailingSlash(path string) string {
	if len(path) > 1 && path[len(path)-1] == '/' {
		return path[:len(path)-1]
	}
	return path
}
<|endoftext|>


<commit_before>package router

import (
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"go.uber.org/zap"

	"context"

	"github.com/aukbit/pluto/reply"
)

//
// HANDLER
//

// Handler is a function type like "net/http" Handler
type Handler interface {
	ServeHTTP(http.ResponseWriter, *http.Request)
}

// HandlerFunc is a function type like "net/http" HandlerFunc
type HandlerFunc func(http.ResponseWriter, *http.Request)

// ServeHTTP calls f(w, r)
func (fn HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fn(w, r)
}

// HandlerErr struct containing an error and helper fields
type HandlerErr struct {
	Error   error
	Message string
	Code    int
	logger  *zap.Logger
}

func NewHandlerErr(err error, code int, l *zap.Logger) *HandlerErr {
	return &HandlerErr{
		Error:   err,
		Message: err.Error(),
		Code:    code,
		logger:  l,
	}
}

// WrapErr reduces the repetition of dealing with errors in Handlers
// returning an error
type WrapErr func(http.ResponseWriter, *http.Request) *HandlerErr

func (fn WrapErr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *HandlerErr, not os.Error.
		// initialize logger if nil
		if e.logger != nil {
			e.logger.Error(e.Message)
		}
		http.Error(w, e.Message, e.Code)
	}
}

//
// KEY
//

// Key router context keys
type Key string

//
// ROUTER
//

// Router ..
type Router struct {
	trie *trie
}

// NewRouter creates a new router instance
func NewRouter() *Router {
	return &Router{trie: newTrie()}
}

// Handle takes a method, pattern, and http handler for a route.
func (r *Router) Handle(method, path string, handler Handler) {
	if matches, err := regexp.MatchString("^[A-Z]+$", method); !matches || err != nil {
		panic("Http method " + method + " is not valid")
	}
	if path[0] != '/' {
		panic("Path must start with /")
	}
	key, value, prefix, vars := transformPath(path)
	data := r.trie.Get(key)
	if data == nil {
		data = newData()
	}
	data.value = value
	data.prefix = prefix
	data.vars = vars
	data.methods[method] = handler.ServeHTTP
	r.trie.Put(key, data)
}

// HandleFunc registers the handler function for the given pattern.
func (r *Router) HandleFunc(method, path string, handlerFn HandlerFunc) {
	r.Handle(method, path, HandlerFunc(handlerFn))
}

// GET is a shortcut for Handle with method "GET"
func (r *Router) GET(path string, handlerFn HandlerFunc) {
	r.HandleFunc("GET", path, handlerFn)
}

// POST is a shortcut for Handle with method "POST"
func (r *Router) POST(path string, handlerFn HandlerFunc) {
	r.HandleFunc("POST", path, handlerFn)
}

// PUT is a shortcut for Handle with method "PUT"
func (r *Router) PUT(path string, handler HandlerFunc) {
	r.HandleFunc("PUT", path, handler)
}

// DELETE is a shortcut for Handle with method "DELETE"
func (r *Router) DELETE(path string, handlerFn HandlerFunc) {
	r.HandleFunc("DELETE", path, handlerFn)
}

// ServeHTTP
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	m := r.findMatch(req)
	if m == nil {
		NotFoundHandler(w, req)
		return
	}
	m.ServeHTTP(w, req)
}

// WrapperMiddleware ..
func (r *Router) WrapperMiddleware(mids ...Middleware) {
	for _, k := range r.trie.Keys() {
		data := r.trie.Get(k)
		for m, h := range data.methods {
			data.methods[m] = Wrap(h, mids...)
			r.trie.Put(k, data)
		}
	}
}

// func (r *Router) findMatch(req *http.Request) *Match {
// 	path := req.URL.Path
// 	method := req.Method
// 	data, values := findData(r, method, path, "", "", "", []string{})
// 	if data != nil {
// 		ctx := setContext(req.Context(), data.vars, values)
// 		handler := data.methods[req.Method]
// 		return &Match{handler: handler, ctx: ctx}
// 	}
// 	return nil
// }

func (r *Router) findMatch(req *http.Request) *Match {
	path := req.URL.Path
	paths := validPaths(path, "", "", "", nil, nil)
	for key, values := range paths {
		data := r.trie.Get(key)
		if data != nil {
			ctx := setContext(req.Context(), data.vars, values)
			handler, ok := data.methods[req.Method]
			if !ok {
				return nil
			}
			return &Match{handler: handler, ctx: ctx}
		}
	}
	return nil
}

// transformPath returns a tuple with key, value, prefix and params for the
// for the presented path
// e.g. /home/:id/room -> /home/:/room, /room, /home/:, [id]
func transformPath(path string) (key, value, prefix string, params []string) {
	params = []string{}
	if path == "/" {
		return "/", "/", "", params
	}
	if path[0] != '/' {
		panic("Path must start with '/'")
	}
	segments := strings.Split(path, "/")[1:]
	for _, s := range segments {
		if s[0] == ':' {
			params = append(params, s[1:])
			path = strings.Replace(path, s, ":", 1)
		}
	}
	key = path
	value = path[strings.LastIndex(key, "/"):]
	if i := strings.LastIndex(key, "/"); i != 0 {
		prefix = path[:i]
	}
	return key, value, prefix, params
}

// validPaths returns candidate paths to be searched on trie
// eg. /home/123/room -> {"/home/123/room":[], "/home/123/:":["room"],
// "/home/:/room":["123"], "/:/123/room":["home"], "/home/:/:":["123","room"],
// "/:/123/:":["home","room"], "/:/:/room":["home","123"], "/:/:/:":["home","123","room"]}
func validPaths(path, key, segment, cutset string, track []string, out map[string][]string) map[string][]string {
	// fmt.Printf("validPaths path %v, key %v, segment %v, cutset %v, track %v, out %v\n", path, key, segment, cutset, track, out)
	if out == nil {
		out = make(map[string][]string)
	}
	if path == "/" {
		out[path] = []string{}
		return out
	}
	// first iteration
	if path != "" && len(key) == 0 {
		// remove trailing slash `/`
		if len(path) > 1 && path[len(path)-1:] == `/` {
			path = path[:len(path)-1]
		}
		key = path
		cutset = path
		out[key] = []string{}
	}
	// all done end iteration
	if strings.Count(path, "/") == strings.Count(key, ":") {
		return out
	}

	// TODO maybe try to use Regex
	i := strings.Index(cutset[1:], "/")
	if i == -1 {
		segment = cutset[1:]
		key = path[:strings.LastIndex(path, segment)] + strings.Replace(path[strings.LastIndex(path, segment):], segment, ":", 1)
		tmp := []string{segment}
		track = append(tmp, track...)
		out[key] = append(out[key], track...)
		// prepare inputs
		cutset = key[:strings.Index(key, "/:")]
		return validPaths(key, key, segment, cutset, track, out)
	}
	segment = cutset[1 : i+1]
	key = strings.Replace(path, segment, ":", 1)
	out[key] = append(out[key], segment)
	out[key] = append(out[key], track...)
	cutset = cutset[i+1:]
	return validPaths(path, key, segment, cutset, track, out)

}

// findData.. deprecated
func findData(r *Router, method, path, suffix, key, segment string, values []string) (*data, []string) {
	// log.Printf("findData method:%v path:%v suffix:%v key:%v segment:%v values:%v\n", method, path, suffix, key, segment, values)
	// initialize
	if path != "" && suffix == "" && key == "" {
		// remove trailing slash `/`
		if len(path) > 1 && strings.LastIndex(path, "/") == len(path)-1 {
			path = path[:len(path)-1]
		}
		suffix = path
		key = path
	}

	// test key
	if key != "" {
		// if key is valid returns

		if d := r.trie.Get(key); d != nil {
			return d, values
		}
		// if d := r.trie.Get(key); d.methods[method] != nil {
		// 	return d, values
		// }

		// Nothing found, returns nil
		if c := strings.Count(key, ":"); c != 0 && c == strings.Count(key, "/") {
			return nil, []string{}
		}

		// restore values
		if len(values) > 1 {
			// remove last segment in the slice
			values = append(values[:len(values)-1], values[:len(values)-2]...)
		} else {
			values = []string{}
		}
	}

	// initialize for the inner loop
	// : stays fixed in the last segment
	if suffix == "" {
		x := strings.Index(key, "/:")
		suffix = key[:x]
		values = append(values, segment)
		path = key
	}

	// TODO maybe try to use Regex
	i := strings.Index(suffix[1:], "/")
	if i == -1 {
		segment = suffix[1:]
		key = path[:strings.LastIndex(path, segment)] + strings.Replace(path[strings.LastIndex(path, segment):], segment, ":", 1)
		// key = strings.Replace(path, segment, ":", 1)
		suffix = ""
	} else {
		segment = suffix[1 : i+1]
		key = strings.Replace(path, segment, ":", 1)
		suffix = suffix[i+1:]
	}
	values = append(values, segment)

	d, v := findData(r, method, path, suffix, key, segment, values)
	return d, v
}

// func setContext(ctx context.Context, vars, values []string) context.Context {
// 	if len(vars) != len(values) {
// 		return ctx
// 	}
// 	for i, value := range values {
// 		// pick opposite var
// 		ctx = context.WithValue(ctx, vars[len(vars)-1-i], value)
// 	}
// 	return ctx
// }

func setContext(ctx context.Context, vars, values []string) context.Context {
	if len(vars) != len(values) {
		return ctx
	}
	for i, value := range values {
		ctx = context.WithValue(ctx, Key(vars[i]), value)
	}
	return ctx
}

// Middleware wraps an http.HandlerFunc with additional
// functionality.
type Middleware func(HandlerFunc) HandlerFunc

// Wrap h with all specified middlewares.
func Wrap(h HandlerFunc, middlewares ...Middleware) HandlerFunc {
	for _, m := range middlewares {
		h = m(h)
	}
	return h
}

// Match wraps an Handler and context
type Match struct {
	handler Handler
	ctx     context.Context
}

func (m *Match) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	m.handler.ServeHTTP(w, req.WithContext(m.ctx))
}

// DefaultRootHandler hello world handler
func DefaultRootHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello World!\n")
}

// NotFoundHandler default not found resource json handler
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
	reply.Json(w, r, http.StatusNotFound, "404 page not found")
}
<commit_msg>router golang.org/x/net/context<commit_after>package router

import (
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"go.uber.org/zap"

	context "golang.org/x/net/context"

	"github.com/aukbit/pluto/reply"
)

//
// HANDLER
//

// Handler is a function type like "net/http" Handler
type Handler interface {
	ServeHTTP(http.ResponseWriter, *http.Request)
}

// HandlerFunc is a function type like "net/http" HandlerFunc
type HandlerFunc func(http.ResponseWriter, *http.Request)

// ServeHTTP calls f(w, r)
func (fn HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fn(w, r)
}

// HandlerErr struct containing an error and helper fields
type HandlerErr struct {
	Error   error
	Message string
	Code    int
	logger  *zap.Logger
}

func NewHandlerErr(err error, code int, l *zap.Logger) *HandlerErr {
	return &HandlerErr{
		Error:   err,
		Message: err.Error(),
		Code:    code,
		logger:  l,
	}
}

// WrapErr reduces the repetition of dealing with errors in Handlers
// returning an error
type WrapErr func(http.ResponseWriter, *http.Request) *HandlerErr

func (fn WrapErr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *HandlerErr, not os.Error.
		// initialize logger if nil
		if e.logger != nil {
			e.logger.Error(e.Message)
		}
		http.Error(w, e.Message, e.Code)
	}
}

//
// KEY
//

// Key router context keys
type Key string

//
// ROUTER
//

// Router ..
type Router struct {
	trie *trie
}

// NewRouter creates a new router instance
func NewRouter() *Router {
	return &Router{trie: newTrie()}
}

// Handle takes a method, pattern, and http handler for a route.
func (r *Router) Handle(method, path string, handler Handler) {
	if matches, err := regexp.MatchString("^[A-Z]+$", method); !matches || err != nil {
		panic("Http method " + method + " is not valid")
	}
	if path[0] != '/' {
		panic("Path must start with /")
	}
	key, value, prefix, vars := transformPath(path)
	data := r.trie.Get(key)
	if data == nil {
		data = newData()
	}
	data.value = value
	data.prefix = prefix
	data.vars = vars
	data.methods[method] = handler.ServeHTTP
	r.trie.Put(key, data)
}

// HandleFunc registers the handler function for the given pattern.
func (r *Router) HandleFunc(method, path string, handlerFn HandlerFunc) {
	r.Handle(method, path, HandlerFunc(handlerFn))
}

// GET is a shortcut for Handle with method "GET"
func (r *Router) GET(path string, handlerFn HandlerFunc) {
	r.HandleFunc("GET", path, handlerFn)
}

// POST is a shortcut for Handle with method "POST"
func (r *Router) POST(path string, handlerFn HandlerFunc) {
	r.HandleFunc("POST", path, handlerFn)
}

// PUT is a shortcut for Handle with method "PUT"
func (r *Router) PUT(path string, handler HandlerFunc) {
	r.HandleFunc("PUT", path, handler)
}

// DELETE is a shortcut for Handle with method "DELETE"
func (r *Router) DELETE(path string, handlerFn HandlerFunc) {
	r.HandleFunc("DELETE", path, handlerFn)
}

// ServeHTTP
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	m := r.findMatch(req)
	if m == nil {
		NotFoundHandler(w, req)
		return
	}
	m.ServeHTTP(w, req)
}

// WrapperMiddleware ..
func (r *Router) WrapperMiddleware(mids ...Middleware) {
	for _, k := range r.trie.Keys() {
		data := r.trie.Get(k)
		for m, h := range data.methods {
			data.methods[m] = Wrap(h, mids...)
			r.trie.Put(k, data)
		}
	}
}

// func (r *Router) findMatch(req *http.Request) *Match {
// 	path := req.URL.Path
// 	method := req.Method
// 	data, values := findData(r, method, path, "", "", "", []string{})
// 	if data != nil {
// 		ctx := setContext(req.Context(), data.vars, values)
// 		handler := data.methods[req.Method]
// 		return &Match{handler: handler, ctx: ctx}
// 	}
// 	return nil
// }

func (r *Router) findMatch(req *http.Request) *Match {
	path := req.URL.Path
	paths := validPaths(path, "", "", "", nil, nil)
	for key, values := range paths {
		data := r.trie.Get(key)
		if data != nil {
			ctx := setContext(req.Context(), data.vars, values)
			handler, ok := data.methods[req.Method]
			if !ok {
				return nil
			}
			return &Match{handler: handler, ctx: ctx}
		}
	}
	return nil
}

// transformPath returns a tuple with key, value, prefix and params for the
// for the presented path
// e.g. /home/:id/room -> /home/:/room, /room, /home/:, [id]
func transformPath(path string) (key, value, prefix string, params []string) {
	params = []string{}
	if path == "/" {
		return "/", "/", "", params
	}
	if path[0] != '/' {
		panic("Path must start with '/'")
	}
	segments := strings.Split(path, "/")[1:]
	for _, s := range segments {
		if s[0] == ':' {
			params = append(params, s[1:])
			path = strings.Replace(path, s, ":", 1)
		}
	}
	key = path
	value = path[strings.LastIndex(key, "/"):]
	if i := strings.LastIndex(key, "/"); i != 0 {
		prefix = path[:i]
	}
	return key, value, prefix, params
}

// validPaths returns candidate paths to be searched on trie
// eg. /home/123/room -> {"/home/123/room":[], "/home/123/:":["room"],
// "/home/:/room":["123"], "/:/123/room":["home"], "/home/:/:":["123","room"],
// "/:/123/:":["home","room"], "/:/:/room":["home","123"], "/:/:/:":["home","123","room"]}
func validPaths(path, key, segment, cutset string, track []string, out map[string][]string) map[string][]string {
	// fmt.Printf("validPaths path %v, key %v, segment %v, cutset %v, track %v, out %v\n", path, key, segment, cutset, track, out)
	if out == nil {
		out = make(map[string][]string)
	}
	if path == "/" {
		out[path] = []string{}
		return out
	}
	// first iteration
	if path != "" && len(key) == 0 {
		// remove trailing slash `/`
		if len(path) > 1 && path[len(path)-1:] == `/` {
			path = path[:len(path)-1]
		}
		key = path
		cutset = path
		out[key] = []string{}
	}
	// all done end iteration
	if strings.Count(path, "/") == strings.Count(key, ":") {
		return out
	}

	// TODO maybe try to use Regex
	i := strings.Index(cutset[1:], "/")
	if i == -1 {
		segment = cutset[1:]
		key = path[:strings.LastIndex(path, segment)] + strings.Replace(path[strings.LastIndex(path, segment):], segment, ":", 1)
		tmp := []string{segment}
		track = append(tmp, track...)
		out[key] = append(out[key], track...)
		// prepare inputs
		cutset = key[:strings.Index(key, "/:")]
		return validPaths(key, key, segment, cutset, track, out)
	}
	segment = cutset[1 : i+1]
	key = strings.Replace(path, segment, ":", 1)
	out[key] = append(out[key], segment)
	out[key] = append(out[key], track...)
	cutset = cutset[i+1:]
	return validPaths(path, key, segment, cutset, track, out)

}

// findData.. deprecated
func findData(r *Router, method, path, suffix, key, segment string, values []string) (*data, []string) {
	// log.Printf("findData method:%v path:%v suffix:%v key:%v segment:%v values:%v\n", method, path, suffix, key, segment, values)
	// initialize
	if path != "" && suffix == "" && key == "" {
		// remove trailing slash `/`
		if len(path) > 1 && strings.LastIndex(path, "/") == len(path)-1 {
			path = path[:len(path)-1]
		}
		suffix = path
		key = path
	}

	// test key
	if key != "" {
		// if key is valid returns

		if d := r.trie.Get(key); d != nil {
			return d, values
		}
		// if d := r.trie.Get(key); d.methods[method] != nil {
		// 	return d, values
		// }

		// Nothing found, returns nil
		if c := strings.Count(key, ":"); c != 0 && c == strings.Count(key, "/") {
			return nil, []string{}
		}

		// restore values
		if len(values) > 1 {
			// remove last segment in the slice
			values = append(values[:len(values)-1], values[:len(values)-2]...)
		} else {
			values = []string{}
		}
	}

	// initialize for the inner loop
	// : stays fixed in the last segment
	if suffix == "" {
		x := strings.Index(key, "/:")
		suffix = key[:x]
		values = append(values, segment)
		path = key
	}

	// TODO maybe try to use Regex
	i := strings.Index(suffix[1:], "/")
	if i == -1 {
		segment = suffix[1:]
		key = path[:strings.LastIndex(path, segment)] + strings.Replace(path[strings.LastIndex(path, segment):], segment, ":", 1)
		// key = strings.Replace(path, segment, ":", 1)
		suffix = ""
	} else {
		segment = suffix[1 : i+1]
		key = strings.Replace(path, segment, ":", 1)
		suffix = suffix[i+1:]
	}
	values = append(values, segment)

	d, v := findData(r, method, path, suffix, key, segment, values)
	return d, v
}

// func setContext(ctx context.Context, vars, values []string) context.Context {
// 	if len(vars) != len(values) {
// 		return ctx
// 	}
// 	for i, value := range values {
// 		// pick opposite var
// 		ctx = context.WithValue(ctx, vars[len(vars)-1-i], value)
// 	}
// 	return ctx
// }

func setContext(ctx context.Context, vars, values []string) context.Context {
	if len(vars) != len(values) {
		return ctx
	}
	for i, value := range values {
		ctx = context.WithValue(ctx, Key(vars[i]), value)
	}
	return ctx
}

// Middleware wraps an http.HandlerFunc with additional
// functionality.
type Middleware func(HandlerFunc) HandlerFunc

// Wrap h with all specified middlewares.
func Wrap(h HandlerFunc, middlewares ...Middleware) HandlerFunc {
	for _, m := range middlewares {
		h = m(h)
	}
	return h
}

// Match wraps an Handler and context
type Match struct {
	handler Handler
	ctx     context.Context
}

func (m *Match) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	m.handler.ServeHTTP(w, req.WithContext(m.ctx))
}

// DefaultRootHandler hello world handler
func DefaultRootHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello World!\n")
}

// NotFoundHandler default not found resource json handler
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
	reply.Json(w, r, http.StatusNotFound, "404 page not found")
}
<|endoftext|>


<commit_before><commit_msg>Use a map[string]string in hasSubtitleParams<commit_after><|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/url"
	"path"

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

var (
	certsDir = flag.String("certs_dir", "", "Directory of certs for TLS connection to AMQP, or empty for non-TLS connection. "+
		"Expected files are: cacert.pem, cert.pem and key.pem.")
	serverName = flag.String("server_name", "", "Name of the server for TLS verification, or empty for default")

	host = flag.String("host", "", "The host:port to tunnel to")
	port = flag.Int("port", 8080, "The local port to listen on")
)

func getTlsConfig() (*tls.Config, error) {
	if *certsDir == "" {
		return nil, nil
	}

	tlscfg := new(tls.Config)
	tlscfg.RootCAs = x509.NewCertPool()
	if ca, err := ioutil.ReadFile(path.Join(*certsDir, "cacert.pem")); err == nil {
		tlscfg.RootCAs.AppendCertsFromPEM(ca)
	} else {
		return nil, fmt.Errorf("Failed reading CA certificate: %v", err)
	}

	if cert, err := tls.LoadX509KeyPair(path.Join(*certsDir, "/cert.pem"), path.Join(*certsDir, "/key.pem")); err == nil {
		tlscfg.Certificates = append(tlscfg.Certificates, cert)
	} else {
		return nil, fmt.Errorf("Failed reading client certificate: %v", err)
	}

	if *serverName != "" {
		tlscfg.ServerName = *serverName
	}
	return tlscfg, nil
}

func getWsConfig() (*websocket.Config, error) {
	url := url.URL{Scheme: "ws", Host: *host}
	if *certsDir != "" {
		url.Scheme = "wss"
	}

	config, err := websocket.NewConfig(url.String(), "http://localhost/")
	if err != nil {
		return nil, err
	}

	if config.TlsConfig, err = getTlsConfig(); err != nil {
		return nil, err
	}

	return config, nil
}

func iocopy(dst io.Writer, src io.Reader, c chan error) {
	_, err := io.Copy(dst, src)
	c <- err
}

type closeable interface {
	CloseWrite() error
}

func closeWrite(conn net.Conn) {
	if closeme, ok := conn.(closeable); ok {
		closeme.CloseWrite()
	}
}

func handleConnection(wsConfig *websocket.Config, conn net.Conn) {
	defer conn.Close()

	tcp, err := proxy.FromEnvironment().Dial("tcp", wsConfig.Location.Host)
	if err != nil {
		log.Print("proxy.FromEnvironment().Dial(): ", err)
		return
	}
	ws, err := websocket.NewClient(wsConfig, tls.Client(tcp, wsConfig.TlsConfig))
	if err != nil {
		log.Print("websocket.NewClient(): ", err)
		return
	}
	defer ws.Close()

	c := make(chan error, 2)
	go iocopy(ws, conn, c)
	go iocopy(conn, ws, c)

	for i := 0; i < 2; i++ {
		if err := <-c; err != nil {
			fmt.Print("io.Copy(): ", err)
			return
		}
		// If any of the sides closes the connection, we want to close the write channel.
		closeWrite(conn)
		closeWrite(tcp)
	}
}

func main() {
	flag.Parse()

	wsConfig, err := getWsConfig()
	if err != nil {
		panic(err)
	}

	ln, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		panic(err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Print("ln.Accept(): ", err)
			continue
		}
		go handleConnection(wsConfig, conn)
	}
}
<commit_msg>renamed 'host' flag to 'target_host'<commit_after>package main

import (
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/url"
	"path"

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

var (
	certsDir = flag.String("certs_dir", "", "Directory of certs for TLS connection to AMQP, or empty for non-TLS connection. "+
		"Expected files are: cacert.pem, cert.pem and key.pem.")
	serverName = flag.String("server_name", "", "Name of the server for TLS verification, or empty for default")

	targetHost = flag.String("target_host", "", "The target host:port to tunnel to")
	port       = flag.Int("port", 8080, "The local port to listen on")
)

func getTlsConfig() (*tls.Config, error) {
	if *certsDir == "" {
		return nil, nil
	}

	tlscfg := new(tls.Config)
	tlscfg.RootCAs = x509.NewCertPool()
	if ca, err := ioutil.ReadFile(path.Join(*certsDir, "cacert.pem")); err == nil {
		tlscfg.RootCAs.AppendCertsFromPEM(ca)
	} else {
		return nil, fmt.Errorf("Failed reading CA certificate: %v", err)
	}

	if cert, err := tls.LoadX509KeyPair(path.Join(*certsDir, "/cert.pem"), path.Join(*certsDir, "/key.pem")); err == nil {
		tlscfg.Certificates = append(tlscfg.Certificates, cert)
	} else {
		return nil, fmt.Errorf("Failed reading client certificate: %v", err)
	}

	if *serverName != "" {
		tlscfg.ServerName = *serverName
	}
	return tlscfg, nil
}

func getWsConfig() (*websocket.Config, error) {
	url := url.URL{Scheme: "ws", Host: *targetHost}
	if *certsDir != "" {
		url.Scheme = "wss"
	}

	config, err := websocket.NewConfig(url.String(), "http://localhost/")
	if err != nil {
		return nil, err
	}

	if config.TlsConfig, err = getTlsConfig(); err != nil {
		return nil, err
	}

	return config, nil
}

func iocopy(dst io.Writer, src io.Reader, c chan error) {
	_, err := io.Copy(dst, src)
	c <- err
}

type closeable interface {
	CloseWrite() error
}

func closeWrite(conn net.Conn) {
	if closeme, ok := conn.(closeable); ok {
		closeme.CloseWrite()
	}
}

func handleConnection(wsConfig *websocket.Config, conn net.Conn) {
	defer conn.Close()

	tcp, err := proxy.FromEnvironment().Dial("tcp", wsConfig.Location.Host)
	if err != nil {
		log.Print("proxy.FromEnvironment().Dial(): ", err)
		return
	}
	ws, err := websocket.NewClient(wsConfig, tls.Client(tcp, wsConfig.TlsConfig))
	if err != nil {
		log.Print("websocket.NewClient(): ", err)
		return
	}
	defer ws.Close()

	c := make(chan error, 2)
	go iocopy(ws, conn, c)
	go iocopy(conn, ws, c)

	for i := 0; i < 2; i++ {
		if err := <-c; err != nil {
			fmt.Print("io.Copy(): ", err)
			return
		}
		// If any of the sides closes the connection, we want to close the write channel.
		closeWrite(conn)
		closeWrite(tcp)
	}
}

func main() {
	flag.Parse()

	wsConfig, err := getWsConfig()
	if err != nil {
		panic(err)
	}

	ln, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		panic(err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Print("ln.Accept(): ", err)
			continue
		}
		go handleConnection(wsConfig, conn)
	}
}
<|endoftext|>


<commit_before>// Package pusher provides a client library for Pusher. It connects to the WebSocket
// interface, allows subscribing to channels, and receiving events.
package pusher

import (
	"encoding/json"
	// "fmt"
	"log"
	s "strings"
	"time"
)

const (
	// Default WebSocket endpoint
	defaultScheme = "wss"
	defaultHost   = "ws.pusherapp.com"
	defaultPort   = "443"
)

// Client responsibilities:
//
// * Connecting (via Connection)
// * Reconnecting on disconnect
// * Decoding and encoding events
// * Managing channel subscriptions
//
type Client struct {
	ClientConfig

	bindings chanbindings

	// Internal channels
	_subscribe   chan *Channel
	_unsubscribe chan string
	_disconnect  chan bool
	_connected   chan bool
	Connected    bool
	Channels     []*Channel
}

type ClientConfig struct {
	Scheme       string
	Host         string
	Port         string
	Key          string
	Secret       string
	AuthEndpoint string
}

type Event struct {
	Name    string `json:"event"`
	Channel string `json:"channel"`
	Data    string `json:"data"`
}

type evBind map[string]chan (string)
type chanbindings map[string]evBind

// New creates a new Pusher client with given Pusher application key
func New(key string) *Client {
	config := ClientConfig{
		Scheme: defaultScheme,
		Host:   defaultHost,
		Port:   defaultPort,
		Key:    key,
	}
	return NewWithConfig(config)
}

// NewWithConfig allows creating a new Pusher client which connects to a custom endpoint
func NewWithConfig(c ClientConfig) *Client {
	client := &Client{
		ClientConfig: c,
		bindings:     make(chanbindings),
		_subscribe:   make(chan *Channel),
		_unsubscribe: make(chan string),
		_disconnect:  make(chan bool),
		_connected:   make(chan bool),
		Channels:     make([]*Channel, 0),
	}
	go client.runLoop()
	return client
}

func (self *Client) Disconnect() {
	self._disconnect <- true
}

// Subscribe subscribes the client to the channel
func (self *Client) Subscribe(channel string) {
	for _, ch := range self.Channels {
		if ch.Name == channel {
			self._subscribe <- ch
			return
		}
	}
	self._subscribe <- &Channel{Name: channel}
}

// UnSubscribe unsubscribes the client from the channel
func (self *Client) Unsubscribe(channel string) {
	self._unsubscribe <- channel
}

func (self *Client) OnChannelEventMessage(channelName, eventName string, c chan string) {
	// Register callback function
	if self.bindings[channelName] == nil {
		self.bindings[channelName] = make(map[string]chan (string))
	}

	self.bindings[channelName][eventName] = c
}

func (self *Client) runLoop() {
	// Run loop state
	// channels := make([]Channel)

	var connection *connection

	onMessage := make(chan string)
	onClose := make(chan bool)
	onDisconnect := make(chan bool)
	callbacks := &connCallbacks{
		onMessage:    onMessage,
		onClose:      onClose,
		onDisconnect: onDisconnect,
	}

	// Connect when this timer fires - initially fire immediately
	connectTimer := time.NewTimer(0 * time.Second)

	for {
		select {
		case <-connectTimer.C:
			// Connect to Pusher
			if c, err := dial(self.ClientConfig, callbacks); err != nil {
				log.Print("Failed to connect: ", err)
				connectTimer.Reset(1 * time.Second)
			} else {
				log.Print("Connection opened")
				connection = c

				// for _, channel := range self.Channels {
				// 	self.subscribe(connection, channel)
				// }

			}

		case c := <-self._subscribe:

			if self.Connected {
				self.subscribe(connection, c)
			}

			self.Channels = append(self.Channels, c)

		case c := <-self._unsubscribe:
			for _, ch := range self.Channels {
				if ch.Name == c {
					ch.Subscribed = false
					if connection != nil {
						self.unsubscribe(connection, ch)
					}
				}
			}

		case <-self._disconnect:
			onDisconnect <- true

		case message := <-onMessage:
			event, _ := decode([]byte(message))
			log.Printf("Received: channel=%v event=%v data=%v", event.Channel, event.Name, event.Data)

			switch event.Name {
			case "pusher:connection_established":
				connectionEstablishedData := make(map[string]string)
				json.Unmarshal([]byte(event.Data), &connectionEstablishedData)
				log.Printf("%+v\n", connectionEstablishedData)
				connection.socketID = connectionEstablishedData["socket_id"]
				self.Connected = true
				for _, ch := range self.Channels {
					if !ch.Subscribed {
						self.subscribe(connection, ch)
					}
				}
			}

			if self.bindings[event.Channel] != nil {
				if self.bindings[event.Channel][event.Name] != nil {
					self.bindings[event.Channel][event.Name] <- event.Data
				}
			}

		case <-onClose:
			log.Print("Connection closed, will reconnect in 1s")
			connection = nil
			connectTimer.Reset(1 * time.Second)

		}
	}
}

func encode(event string, data interface{}) (message []byte, err error) {
	message, err = json.Marshal(map[string]interface{}{
		"event": event,
		"data":  data,
	})
	return
}

func decode(message []byte) (event Event, err error) {
	err = json.Unmarshal(message, &event)
	return
}

func isPrivateChannel(name string) bool {
	return s.HasPrefix(name, "private-")
}

func (self *Client) subscribe(conn *connection, channel *Channel) {
	log.Println(channel.Name)
	payload := map[string]string{
		"channel": channel.Name,
	}

	if isPrivateChannel(channel.Name) {
		stringToSign := s.Join([]string{conn.socketID, channel.Name}, ":")
		log.Printf("stringToSign: %s", stringToSign)
		authString := createAuthString(self.Key, self.ClientConfig.Secret, stringToSign)
		payload["auth"] = authString
	}

	log.Printf("%+v\n", payload)

	message, _ := encode("pusher:subscribe", payload)
	conn.send(message)
	channel.Subscribed = true
}

func (self *Client) unsubscribe(conn *connection, channel *Channel) {
	message, _ := encode("pusher:unsubscribe", map[string]string{
		"channel": channel.Name,
	})
	conn.send(message)
}
<commit_msg>Channel.Subscribed set to true only when subscription_succeeded<commit_after>// Package pusher provides a client library for Pusher. It connects to the WebSocket
// interface, allows subscribing to channels, and receiving events.
package pusher

import (
	"encoding/json"
	// "fmt"
	"log"
	s "strings"
	"time"
)

const (
	// Default WebSocket endpoint
	defaultScheme = "wss"
	defaultHost   = "ws.pusherapp.com"
	defaultPort   = "443"
)

// Client responsibilities:
//
// * Connecting (via Connection)
// * Reconnecting on disconnect
// * Decoding and encoding events
// * Managing channel subscriptions
//
type Client struct {
	ClientConfig

	bindings chanbindings

	// Internal channels
	_subscribe   chan *Channel
	_unsubscribe chan string
	_disconnect  chan bool
	_connected   chan bool
	Connected    bool
	Channels     []*Channel
}

type ClientConfig struct {
	Scheme       string
	Host         string
	Port         string
	Key          string
	Secret       string
	AuthEndpoint string
}

type Event struct {
	Name    string `json:"event"`
	Channel string `json:"channel"`
	Data    string `json:"data"`
}

type evBind map[string]chan (string)
type chanbindings map[string]evBind

// New creates a new Pusher client with given Pusher application key
func New(key string) *Client {
	config := ClientConfig{
		Scheme: defaultScheme,
		Host:   defaultHost,
		Port:   defaultPort,
		Key:    key,
	}
	return NewWithConfig(config)
}

// NewWithConfig allows creating a new Pusher client which connects to a custom endpoint
func NewWithConfig(c ClientConfig) *Client {
	client := &Client{
		ClientConfig: c,
		bindings:     make(chanbindings),
		_subscribe:   make(chan *Channel),
		_unsubscribe: make(chan string),
		_disconnect:  make(chan bool),
		_connected:   make(chan bool),
		Channels:     make([]*Channel, 0),
	}
	go client.runLoop()
	return client
}

func (self *Client) Disconnect() {
	self._disconnect <- true
}

// Subscribe subscribes the client to the channel
func (self *Client) Subscribe(channel string) {
	for _, ch := range self.Channels {
		if ch.Name == channel {
			self._subscribe <- ch
			return
		}
	}
	self._subscribe <- &Channel{Name: channel}
}

// UnSubscribe unsubscribes the client from the channel
func (self *Client) Unsubscribe(channel string) {
	self._unsubscribe <- channel
}

func (self *Client) OnChannelEventMessage(channelName, eventName string, c chan string) {
	// Register callback function
	if self.bindings[channelName] == nil {
		self.bindings[channelName] = make(map[string]chan (string))
	}

	self.bindings[channelName][eventName] = c
}

func (self *Client) runLoop() {
	// Run loop state
	// channels := make([]Channel)

	var connection *connection

	onMessage := make(chan string)
	onClose := make(chan bool)
	onDisconnect := make(chan bool)
	callbacks := &connCallbacks{
		onMessage:    onMessage,
		onClose:      onClose,
		onDisconnect: onDisconnect,
	}

	// Connect when this timer fires - initially fire immediately
	connectTimer := time.NewTimer(0 * time.Second)

	for {
		select {
		case <-connectTimer.C:
			// Connect to Pusher
			if c, err := dial(self.ClientConfig, callbacks); err != nil {
				log.Print("Failed to connect: ", err)
				connectTimer.Reset(1 * time.Second)
			} else {
				log.Print("Connection opened")
				connection = c

				// for _, channel := range self.Channels {
				// 	self.subscribe(connection, channel)
				// }

			}

		case c := <-self._subscribe:

			if self.Connected {
				self.subscribe(connection, c)
			}

			self.Channels = append(self.Channels, c)

		case c := <-self._unsubscribe:
			for _, ch := range self.Channels {
				if ch.Name == c {
					ch.Subscribed = false
					if connection != nil {
						self.unsubscribe(connection, ch)
					}
				}
			}

		case <-self._disconnect:
			onDisconnect <- true

		case message := <-onMessage:
			event, _ := decode([]byte(message))
			log.Printf("Received: channel=%v event=%v data=%v", event.Channel, event.Name, event.Data)

			switch event.Name {
			case "pusher:connection_established":
				connectionEstablishedData := make(map[string]string)
				json.Unmarshal([]byte(event.Data), &connectionEstablishedData)
				log.Printf("%+v\n", connectionEstablishedData)
				connection.socketID = connectionEstablishedData["socket_id"]
				self.Connected = true
				for _, ch := range self.Channels {
					if !ch.Subscribed {
						self.subscribe(connection, ch)
					}
				}

			case "pusher_internal:subscription_succeeded":
				for _, ch := range self.Channels {
					if ch.Name == event.Channel {
						ch.Subscribed = true
					}
				}
			}

			if self.bindings[event.Channel] != nil {
				if self.bindings[event.Channel][event.Name] != nil {
					self.bindings[event.Channel][event.Name] <- event.Data
				}
			}

		case <-onClose:
			log.Print("Connection closed, will reconnect in 1s")
			connection = nil
			connectTimer.Reset(1 * time.Second)

		}
	}
}

func encode(event string, data interface{}) (message []byte, err error) {
	message, err = json.Marshal(map[string]interface{}{
		"event": event,
		"data":  data,
	})
	return
}

func decode(message []byte) (event Event, err error) {
	err = json.Unmarshal(message, &event)
	return
}

func isPrivateChannel(name string) bool {
	return s.HasPrefix(name, "private-")
}

func (self *Client) subscribe(conn *connection, channel *Channel) {
	log.Println(channel.Name)
	payload := map[string]string{
		"channel": channel.Name,
	}

	if isPrivateChannel(channel.Name) {
		stringToSign := s.Join([]string{conn.socketID, channel.Name}, ":")
		log.Printf("stringToSign: %s", stringToSign)
		authString := createAuthString(self.Key, self.ClientConfig.Secret, stringToSign)
		payload["auth"] = authString
	}

	log.Printf("%+v\n", payload)

	message, _ := encode("pusher:subscribe", payload)
	conn.send(message)
}

func (self *Client) unsubscribe(conn *connection, channel *Channel) {
	message, _ := encode("pusher:unsubscribe", map[string]string{
		"channel": channel.Name,
	})
	conn.send(message)
}
<|endoftext|>


<commit_before>package fitsio

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/gonuts/binary"
)

// Column represents a column in a FITS table
type Column struct {
	Name    string  // column name, corresponding to ``TTYPE`` keyword
	Format  string  // column format, corresponding to ``TFORM`` keyword
	Unit    string  // column unit, corresponding to ``TUNIT`` keyword
	Null    string  // null value, corresponding to ``TNULL`` keyword
	Bscale  float64 // bscale value, corresponding to ``TSCAL`` keyword
	Bzero   float64 // bzero value, corresponding to ``TZERO`` keyword
	Display string  // display format, corresponding to ``TDISP`` keyword
	Dim     []int64 // column dimension corresponding to ``TDIM`` keyword
	Start   int64   // column starting position, corresponding to ``TBCOL`` keyword

	dtype  Type   // type of the value held by the column
	offset int    // offset in bytes (from start of data-pad or heap-pad) to get at the column data
	txtfmt string // go-based fmt string for ASCII table

	// read function (binary/ascii)
	read func(table *Table, icol int, irow int64, ptr interface{}) error

	// write function (binary/ascii)
	write func(table *Table, icol int, irow int64, ptr interface{}) error
}

// NewColumn creates a new Column with name `name` and Format inferred from the type of value
func NewColumn(name string, v interface{}) (Column, error) {
	panic("not implemented")
}

// Type returns the Go reflect.Type associated with this Column
func (col *Column) Type() reflect.Type {
	return col.dtype.gotype
}

// readBin reads the value at column number icol and row irow, into ptr.
func (col *Column) readBin(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	switch rt.Kind() {
	case reflect.Slice:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize
		row := table.data[beg:end]
		buf := bytes.NewBuffer(row)
		bdec := binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian

		slice := reflect.ValueOf(ptr).Elem()
		nmax := 0

		switch col.dtype.dsize {
		case 8:
			var n int32
			var offset int32
			err = bdec.Decode(&n)
			if err != nil {
				return fmt.Errorf("**error** heap-size val=%#v (%T) %v\n", ptr, ptr, err)
			}
			err = bdec.Decode(&offset)
			if err != nil {
				return fmt.Errorf("**error** heap-offset val=%#v (%T) %v\n", ptr, ptr, err)
			}
			beg = int(offset)
			end = beg + int(n)*int(col.dtype.gotype.Elem().Size())
			nmax = int(n)

		case 16:
			var n int64
			var offset int64
			err = bdec.Decode(&n)
			if err != nil {
				return fmt.Errorf("**error** heap-size val=%#v (%T) %v\n", ptr, ptr, err)
			}
			err = bdec.Decode(&offset)
			if err != nil {
				return fmt.Errorf("**error** heap-offset val=%#v (%T) %v\n", ptr, ptr, err)
			}
			beg = int(offset)
			end = beg + int(n)*int(col.dtype.gotype.Elem().Size())
			nmax = int(n)
		}

		buf = bytes.NewBuffer(table.heap[beg:end])
		bdec = binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian

		slice.SetLen(0)
		for i := 0; i < nmax; i++ {
			vv := reflect.New(rt.Elem())
			err = bdec.Decode(vv.Interface())
			if err != nil {
				return fmt.Errorf("fitsio: problem encoding: %v", err)
			}
			slice = reflect.Append(slice, vv.Elem())
		}
		if err != nil {
			return fmt.Errorf("**error** val=%#v (%T) %v\n", ptr, ptr, err)
		}
		rv := reflect.ValueOf(ptr)
		rv.Elem().Set(slice)

	case reflect.Array:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + (col.dtype.dsize * col.dtype.len)
		row := table.data[beg:end]
		buf := bytes.NewBuffer(row)
		bdec := binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian

		err = bdec.Decode(ptr)
		if err != nil {
			return fmt.Errorf("**error** val=%#v (%T) %v\n", ptr, ptr, err)
		}

	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.Complex64, reflect.Complex128:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize
		row := table.data[beg:end]
		buf := bytes.NewBuffer(row)
		bdec := binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian
		err = bdec.Decode(ptr)
		if err != nil {
			return fmt.Errorf("**error** val=%#v (%T) %v\n", ptr, ptr, err)
		}

	case reflect.String:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize
		row := table.data[beg:end]
		str := ""
		if row[0] == '\x00' {
			str = string(row[1:])
			str = strings.TrimRight(str, string([]byte("\x00")))
		} else {
			str = string(row)
		}

		rv.SetString(str)

	default:
		return fmt.Errorf("fitsio: binary-table can not read/write %v", rt.Kind())
	}
	return err
}

// writeBin writes the value at column number icol and row irow, from ptr.
func (col *Column) writeBin(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	switch rt.Kind() {
	case reflect.Slice:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize

		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}
		benc := binary.NewEncoder(buf)
		benc.Order = binary.BigEndian

		slice := reflect.ValueOf(ptr).Elem()
		nmax := slice.Len()

		switch col.dtype.dsize {
		case 8:
			data := [2]int32{int32(nmax), int32(len(table.heap))}
			err = benc.Encode(&data)
			if err != nil {
				return fmt.Errorf("**error** heap-size/offset val=%#v (%T) %v\n", ptr, ptr, err)
			}

		case 16:
			data := [2]int64{int64(nmax), int64(len(table.heap))}
			err = benc.Encode(&data)
			if err != nil {
				return fmt.Errorf("**error** heap-size/offset val=%#v (%T) %v\n", ptr, ptr, err)
			}
		}

		{
			buf := new(bytes.Buffer)
			buf.Grow(nmax * col.dtype.hsize)
			benc = binary.NewEncoder(buf)
			benc.Order = binary.BigEndian

			for i := 0; i < nmax; i++ {
				err = benc.Encode(slice.Index(i).Addr().Interface())
				if err != nil {
					return fmt.Errorf("fitsio: problem encoding: %v", err)
				}
			}
			table.heap = append(table.heap, buf.Bytes()...)
		}

	case reflect.Array:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + (col.dtype.dsize * col.dtype.len)

		//buf := new(bytes.Buffer)
		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}
		benc := binary.NewEncoder(buf)
		benc.Order = binary.BigEndian
		err = benc.Encode(ptr)
		if err != nil {
			return fmt.Errorf("**error** val=%#v (%T) %v\n", ptr, ptr, err)
		}

	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.Complex64, reflect.Complex128:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize

		//buf := bytes.NewBuffer(row)
		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}

		benc := binary.NewEncoder(buf)
		benc.Order = binary.BigEndian
		err = benc.Encode(ptr)

	case reflect.String:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize

		//buf := bytes.NewBuffer(row)
		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}

		str := rv.String()
		data := make([]byte, 0, len(str)+1)
		data = append(data, '\x00')
		data = append(data, []byte(str)...)
		n := len(data)
		if n > end-beg {
			n = end - beg
		}
		if n < end-beg {
			data = append(data, bytes.Repeat([]byte("\x00"), end-beg-n)...)
			n = len(data)
		}
		_, err = buf.Write(data[:n])

		if err != nil {
			return fmt.Errorf("**error** val=%#v (%T) %v\n", ptr, ptr, err)
		}

	default:
		return fmt.Errorf("fitsio: binary-table can not read/write %v", rt.Kind())
	}

	return err
}

// readTxt reads the value at column number icol and row irow, into ptr.
func (col *Column) readTxt(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	beg := table.rowsz*int(irow) + col.offset
	end := beg + col.dtype.dsize
	buf := table.data[beg:end]
	str := strings.TrimSpace(string(buf))

	switch rt.Kind() {
	case reflect.Slice:

		return fmt.Errorf("fitsio: ASCII-table can not read/write slices")

	case reflect.Array:

		return fmt.Errorf("fitsio: ASCII-table can not read/write arrays")

	case reflect.Bool:

		return fmt.Errorf("fitsio: ASCII-table can not read/write booleans")

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		v, err := strconv.ParseUint(str, 10, 64)
		if err != nil {
			return fmt.Errorf("fitsio: error parsing %q into a uint: %v", str, err)
		}
		rv.SetUint(v)

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v, err := strconv.ParseInt(str, 10, 64)
		if err != nil {
			return fmt.Errorf("fitsio: error parsing %q into an int: %v", str, err)
		}
		rv.SetInt(v)

	case reflect.Float32, reflect.Float64:

		v, err := strconv.ParseFloat(str, 64)
		if err != nil {
			return fmt.Errorf("fitsio: error parsing %q into a float: %v", str, err)
		}
		rv.SetFloat(v)

	case reflect.Complex64, reflect.Complex128:

		return fmt.Errorf("fitsio: ASCII-table can not read/write complexes")

	case reflect.String:
		rv.SetString(str)

	default:
		return fmt.Errorf("fitsio: ASCII-table can not read/write %v", rt.Kind())
	}
	return err
}

// writeTxt writes the value at column number icol and row irow, from ptr.
func (col *Column) writeTxt(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	beg := table.rowsz*int(irow) + col.offset
	end := beg + col.dtype.dsize
	buf := &sectionWriter{
		buf: table.data[beg:end],
		beg: 0,
	}

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	switch rt.Kind() {
	case reflect.Slice:

		return fmt.Errorf("fitsio: ASCII-table can not read/write slices")

	case reflect.Array:

		return fmt.Errorf("fitsio: ASCII-table can not read/write arrays")

	case reflect.Bool:

		return fmt.Errorf("fitsio: ASCII-table can not read/write booleans")

	case reflect.Complex64, reflect.Complex128:

		return fmt.Errorf("fitsio: ASCII-table can not read/write complexes")

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Float32, reflect.Float64,
		reflect.String:

		str := fmt.Sprintf(col.txtfmt, rv.Interface())
		n := 0
		n, err = fmt.Fprintf(buf, str)
		if err != nil {
			return fmt.Errorf("fitsio: error writing '%#v': %v", rv.Interface(), err)
		}
		if n != len(buf.buf) {
			return fmt.Errorf(
				"fitsio: error writing '%#v'. expected %d bytes. wrote %d",
				rv.Interface(), len(buf.buf), n,
			)
		}

	default:
		return fmt.Errorf("fitsio: ASCII-table can not read/write %v", rt.Kind())
	}

	return err
}
<commit_msg>column: better error messages<commit_after>package fitsio

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/gonuts/binary"
)

// Column represents a column in a FITS table
type Column struct {
	Name    string  // column name, corresponding to ``TTYPE`` keyword
	Format  string  // column format, corresponding to ``TFORM`` keyword
	Unit    string  // column unit, corresponding to ``TUNIT`` keyword
	Null    string  // null value, corresponding to ``TNULL`` keyword
	Bscale  float64 // bscale value, corresponding to ``TSCAL`` keyword
	Bzero   float64 // bzero value, corresponding to ``TZERO`` keyword
	Display string  // display format, corresponding to ``TDISP`` keyword
	Dim     []int64 // column dimension corresponding to ``TDIM`` keyword
	Start   int64   // column starting position, corresponding to ``TBCOL`` keyword

	dtype  Type   // type of the value held by the column
	offset int    // offset in bytes (from start of data-pad or heap-pad) to get at the column data
	txtfmt string // go-based fmt string for ASCII table

	// read function (binary/ascii)
	read func(table *Table, icol int, irow int64, ptr interface{}) error

	// write function (binary/ascii)
	write func(table *Table, icol int, irow int64, ptr interface{}) error
}

// NewColumn creates a new Column with name `name` and Format inferred from the type of value
func NewColumn(name string, v interface{}) (Column, error) {
	panic("not implemented")
}

// Type returns the Go reflect.Type associated with this Column
func (col *Column) Type() reflect.Type {
	return col.dtype.gotype
}

// readBin reads the value at column number icol and row irow, into ptr.
func (col *Column) readBin(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	switch rt.Kind() {
	case reflect.Slice:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize
		row := table.data[beg:end]
		buf := bytes.NewBuffer(row)
		bdec := binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian

		slice := reflect.ValueOf(ptr).Elem()
		nmax := 0

		switch col.dtype.dsize {
		case 8:
			var n int32
			var offset int32
			err = bdec.Decode(&n)
			if err != nil {
				return fmt.Errorf("fitsio: problem decoding slice 32b-length: %v\n", err)
			}
			err = bdec.Decode(&offset)
			if err != nil {
				return fmt.Errorf("fitsio: problem decoding slice 32b-offset: %v\n", err)
			}
			beg = int(offset)
			end = beg + int(n)*int(col.dtype.gotype.Elem().Size())
			nmax = int(n)

		case 16:
			var n int64
			var offset int64
			err = bdec.Decode(&n)
			if err != nil {
				return fmt.Errorf("fitsio: problem decoding slice 64b-length: %v\n", err)
			}
			err = bdec.Decode(&offset)
			if err != nil {
				return fmt.Errorf("fitsio: problem decoding slice 64b-offset: %v\n", err)
			}
			beg = int(offset)
			end = beg + int(n)*int(col.dtype.gotype.Elem().Size())
			nmax = int(n)
		}

		buf = bytes.NewBuffer(table.heap[beg:end])
		bdec = binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian

		slice.SetLen(0)
		for i := 0; i < nmax; i++ {
			vv := reflect.New(rt.Elem())
			err = bdec.Decode(vv.Interface())
			if err != nil {
				return fmt.Errorf("fitsio: problem encoding: %v", err)
			}
			slice = reflect.Append(slice, vv.Elem())
		}
		if err != nil {
			return fmt.Errorf("fitsio: %v\n", err)
		}
		rv := reflect.ValueOf(ptr)
		rv.Elem().Set(slice)

	case reflect.Array:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + (col.dtype.dsize * col.dtype.len)
		row := table.data[beg:end]
		buf := bytes.NewBuffer(row)
		bdec := binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian

		err = bdec.Decode(ptr)
		if err != nil {
			return fmt.Errorf("fitsio: %v\n", err)
		}

	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.Complex64, reflect.Complex128:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize
		row := table.data[beg:end]
		buf := bytes.NewBuffer(row)
		bdec := binary.NewDecoder(buf)
		bdec.Order = binary.BigEndian
		err = bdec.Decode(ptr)
		if err != nil {
			return fmt.Errorf("fitsio: %v\n", err)
		}

	case reflect.String:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize
		row := table.data[beg:end]
		str := ""
		if row[0] == '\x00' {
			str = string(row[1:])
			str = strings.TrimRight(str, string([]byte("\x00")))
		} else {
			str = string(row)
		}

		rv.SetString(str)

	default:
		return fmt.Errorf("fitsio: binary-table can not read/write %v", rt.Kind())
	}
	return err
}

// writeBin writes the value at column number icol and row irow, from ptr.
func (col *Column) writeBin(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	switch rt.Kind() {
	case reflect.Slice:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize

		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}
		benc := binary.NewEncoder(buf)
		benc.Order = binary.BigEndian

		slice := reflect.ValueOf(ptr).Elem()
		nmax := slice.Len()

		switch col.dtype.dsize {
		case 8:
			data := [2]int32{int32(nmax), int32(len(table.heap))}
			err = benc.Encode(&data)
			if err != nil {
				return fmt.Errorf("fitsio: problem encoding slice 32b-descriptor: %v\n", err)
			}

		case 16:
			data := [2]int64{int64(nmax), int64(len(table.heap))}
			err = benc.Encode(&data)
			if err != nil {
				return fmt.Errorf("fitsio: problem encoding slice 64b-descriptor: %v\n", err)
			}
		}

		{
			buf := new(bytes.Buffer)
			buf.Grow(nmax * col.dtype.hsize)
			benc = binary.NewEncoder(buf)
			benc.Order = binary.BigEndian

			for i := 0; i < nmax; i++ {
				err = benc.Encode(slice.Index(i).Addr().Interface())
				if err != nil {
					return fmt.Errorf("fitsio: problem encoding: %v", err)
				}
			}
			table.heap = append(table.heap, buf.Bytes()...)
		}

	case reflect.Array:

		beg := table.rowsz*int(irow) + col.offset
		end := beg + (col.dtype.dsize * col.dtype.len)

		//buf := new(bytes.Buffer)
		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}
		benc := binary.NewEncoder(buf)
		benc.Order = binary.BigEndian
		err = benc.Encode(ptr)
		if err != nil {
			return fmt.Errorf("fitsio: %v\n", err)
		}

	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.Complex64, reflect.Complex128:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize

		//buf := bytes.NewBuffer(row)
		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}

		benc := binary.NewEncoder(buf)
		benc.Order = binary.BigEndian
		err = benc.Encode(ptr)

	case reflect.String:

		//scalar := true
		//err = col.decode(table, n, rt, rv, scalar)

		beg := table.rowsz*int(irow) + col.offset
		end := beg + col.dtype.dsize

		//buf := bytes.NewBuffer(row)
		buf := &sectionWriter{
			buf: table.data[beg:end],
			beg: 0,
		}

		str := rv.String()
		data := make([]byte, 0, len(str)+1)
		data = append(data, '\x00')
		data = append(data, []byte(str)...)
		n := len(data)
		if n > end-beg {
			n = end - beg
		}
		if n < end-beg {
			data = append(data, bytes.Repeat([]byte("\x00"), end-beg-n)...)
			n = len(data)
		}
		_, err = buf.Write(data[:n])

		if err != nil {
			return fmt.Errorf("fitsio: %v\n", err)
		}

	default:
		return fmt.Errorf("fitsio: binary-table can not read/write %v", rt.Kind())
	}

	return err
}

// readTxt reads the value at column number icol and row irow, into ptr.
func (col *Column) readTxt(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	beg := table.rowsz*int(irow) + col.offset
	end := beg + col.dtype.dsize
	buf := table.data[beg:end]
	str := strings.TrimSpace(string(buf))

	switch rt.Kind() {
	case reflect.Slice:

		return fmt.Errorf("fitsio: ASCII-table can not read/write slices")

	case reflect.Array:

		return fmt.Errorf("fitsio: ASCII-table can not read/write arrays")

	case reflect.Bool:

		return fmt.Errorf("fitsio: ASCII-table can not read/write booleans")

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		v, err := strconv.ParseUint(str, 10, 64)
		if err != nil {
			return fmt.Errorf("fitsio: error parsing %q into a uint: %v", str, err)
		}
		rv.SetUint(v)

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v, err := strconv.ParseInt(str, 10, 64)
		if err != nil {
			return fmt.Errorf("fitsio: error parsing %q into an int: %v", str, err)
		}
		rv.SetInt(v)

	case reflect.Float32, reflect.Float64:

		v, err := strconv.ParseFloat(str, 64)
		if err != nil {
			return fmt.Errorf("fitsio: error parsing %q into a float: %v", str, err)
		}
		rv.SetFloat(v)

	case reflect.Complex64, reflect.Complex128:

		return fmt.Errorf("fitsio: ASCII-table can not read/write complexes")

	case reflect.String:
		rv.SetString(str)

	default:
		return fmt.Errorf("fitsio: ASCII-table can not read/write %v", rt.Kind())
	}
	return err
}

// writeTxt writes the value at column number icol and row irow, from ptr.
func (col *Column) writeTxt(table *Table, icol int, irow int64, ptr interface{}) error {
	var err error

	beg := table.rowsz*int(irow) + col.offset
	end := beg + col.dtype.dsize
	buf := &sectionWriter{
		buf: table.data[beg:end],
		beg: 0,
	}

	rv := reflect.Indirect(reflect.ValueOf(ptr))
	rt := reflect.TypeOf(rv.Interface())

	switch rt.Kind() {
	case reflect.Slice:

		return fmt.Errorf("fitsio: ASCII-table can not read/write slices")

	case reflect.Array:

		return fmt.Errorf("fitsio: ASCII-table can not read/write arrays")

	case reflect.Bool:

		return fmt.Errorf("fitsio: ASCII-table can not read/write booleans")

	case reflect.Complex64, reflect.Complex128:

		return fmt.Errorf("fitsio: ASCII-table can not read/write complexes")

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Float32, reflect.Float64,
		reflect.String:

		str := fmt.Sprintf(col.txtfmt, rv.Interface())
		n := 0
		n, err = fmt.Fprintf(buf, str)
		if err != nil {
			return fmt.Errorf("fitsio: error writing '%#v': %v", rv.Interface(), err)
		}
		if n != len(buf.buf) {
			return fmt.Errorf(
				"fitsio: error writing '%#v'. expected %d bytes. wrote %d",
				rv.Interface(), len(buf.buf), n,
			)
		}

	default:
		return fmt.Errorf("fitsio: ASCII-table can not read/write %v", rt.Kind())
	}

	return err
}
<|endoftext|>


<commit_before><commit_msg>update server<commit_after><|endoftext|>